JezK
Edit File: img-optm-manage.trait.php
<?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_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_optming table $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 scanned images $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, ]; } // 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 * * The toggle direction is derived from the main file's on-disk state (which is exactly what the Media Library row status reflects) and then applied uniformly to the main file and all sizes, mirroring the batch switch. * * @since 1.6.2 * @access private * @param string $type The switch type: origXXX / webpXXX / avifXXX where XXX is the post ID. */ private function _switch_optm_file( $type ) { global $wpdb; $switch_type = substr( $type, 0, 4 ); // 'orig' | 'webp' | 'avif' $pid = (int) substr( $type, 4 ); if ( ! $pid ) { return; } self::debug( 'Switch single image [type] ' . $switch_type . ' [pid] ' . $pid ); // Load the attachment meta (main file + thumbnail sizes), mirroring reset_row() $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 $v = $wpdb->get_row( $wpdb->prepare( $img_q, [ $pid ] ) ); $meta_value = $this->_parse_wp_meta_value( $v ); if ( ! $meta_value ) { return; } $this->tmp_pid = $pid; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; if ( 'orig' === $switch_type ) { // Detect direction from the main file's backups: bk = backup-of-original (currently optimized), bk.optm = backup-of-optimized (currently original) $extension = pathinfo( $meta_value['file'], PATHINFO_EXTENSION ); $local_filename = substr( $meta_value['file'], 0, -strlen( $extension ) - 1 ); if ( $this->__media->info( $local_filename . '.bk.' . $extension, $this->tmp_pid ) ) { $this->tmp_type = 'orig'; $msg = __( 'Restored original file successfully.', 'litespeed-cache' ); } elseif ( $this->__media->info( $local_filename . '.bk.optm.' . $extension, $this->tmp_pid ) ) { $this->tmp_type = 'optm'; $msg = __( 'Switched to optimized file successfully.', 'litespeed-cache' ); } else { self::debug( 'Nothing to switch for original [pid] ' . $pid ); return; } $this->_switch_bk_file( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { array_map( [ $this, '_switch_bk_file' ], $meta_value['sizes'] ); } } else { // webp/avif: detect direction from the main file's sidecar. Served = <file>.<fmt>, parked/disabled = <file>.optm.<fmt> $this->_tmp_switch_format = $switch_type; $is_avif = 'avif' === $switch_type; if ( $this->__media->info( $meta_value['file'] . '.' . $switch_type, $this->tmp_pid ) ) { $this->tmp_type = 'orig'; // currently served -> disable $msg = $is_avif ? __( 'Disabled AVIF file successfully.', 'litespeed-cache' ) : __( 'Disabled WebP file successfully.', 'litespeed-cache' ); } elseif ( $this->__media->info( $meta_value['file'] . '.optm.' . $switch_type, $this->tmp_pid ) ) { $this->tmp_type = 'optm'; // currently parked -> enable $msg = $is_avif ? __( 'Enabled AVIF file successfully.', 'litespeed-cache' ) : __( 'Enabled WebP file successfully.', 'litespeed-cache' ); } else { self::debug( 'Nothing to switch for ' . $switch_type . ' [pid] ' . $pid ); return; } $this->_switch_format_file( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { array_map( [ $this, '_switch_format_file' ], $meta_value['sizes'] ); } } Admin_Display::success( $msg ); } /** * Switch one file's next-gen sidecar between the served and parked (disabled) name, per `$this->_tmp_switch_format` and direction `$this->tmp_type`. Mirrors `_switch_bk_file()` for webp/avif. * * @since 7.9 * @access private * @param array $meta_value The meta value array containing file info. * @param bool $is_ori_file Whether this is the original (main) file. */ private function _switch_format_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; } $served_file = $short_file_path . '.' . $this->_tmp_switch_format; // active, served to visitors $parked_file = $short_file_path . '.optm.' . $this->_tmp_switch_format; // disabled backup if ( 'optm' === $this->tmp_type ) { // enable: parked -> served if ( $this->__media->info( $parked_file, $this->tmp_pid ) ) { $this->__media->rename( $parked_file, $served_file, $this->tmp_pid ); } } elseif ( $this->__media->info( $served_file, $this->tmp_pid ) ) { // disable: served -> parked $this->__media->rename( $served_file, $parked_file, $this->tmp_pid ); } } /** * 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_optming table 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 ); } } }
BanaBridge News
https://validator.w3.org/feed/docs/rss2.html
Court Orders Ex-Weah Protocol Chief Jailed on US$8 Million Bail, Sends 15 Jurors to Prison Over Misconduct
Women Renew Call for War and Economic Crimes Court at Liberia’s Historic Peace Prayer Site
Finance Minister Ngafuan Urges Transparency, Accountability as Liberia Advances ARREST Agenda at UN Retreat
PPCC Donates ICT Equipment to 50 Government Institutions to Expand Electronic Procurement System
Liberia Commissions €100,000 Elemental Analyzer to Strengthen Environmental Research and Food Security
EPA Ends 16 Years of Renting with Purchase of Permanent Headquarters
West African Tax Bodies Deepen Partnership to Boost Revenue Mobilization, Launch Carbon Tax Study
Liberia Hosts Sierra Leone Procurement Delegation for E-GP Reform Study Tour
World Bank Praises Liberia’s Central Bank for Economic Stability and Private Sector Progress
Liberians See Slight Improvement in Corruption Fight, But Graft Still Viewed as Widespread, CENTAL Report Finds
Canada Afrique Care Foundation Set for Official Launch, Expanding Free Mental Health Access Across Canada and Africa
Weah Claims Liberia Has Become a “Narco State” as Opposition Rallies Behind Calls for Independent Probe into US$19 Million Cocaine Bust
EPA Boss Calls for Stronger Student Engagement, Unveils Push for Academic Partnership at UL Climate Symposium
Education Minister Urges Students to Embrace Tax Responsibility at National Student Tax Day
CDC Secretary General Urges U.S. Government to Review Support to Liberia National Police
Liberia Partners with CACFO to Expand Mental Health Services Ahead of Global Launch
Wreh-Toe Appointed Board Chair of CACFO as Foundation Strengthens Mental Health Push
GIABA Opens New Information Center Headquarters in Abidjan
Liberia Wins Fresh Backing as World Bank Group Executive Praises Progress, Pushes Faster Project Delivery
LRA Honors Liberia’s Top Taxpayers as Revenue Collections Hit Historic High
Root Cause in Question as Border Tensions Persist After Conakry Peace Deal
From Enforcement to Innovation: EPA Marks 2025 as a Defining Year for Liberia’s Environment
Weah Declares ‘Road to 2029 Begins Today’ as CDC Marks 22nd Anniversary in Zwedru
500 Documented Governance Failures: Activist Publishes Sweeping End-of-Year Indictment of Boakai Administration
CDC Calls for Probe into Border Crisis, Cites Alleged Mining Activities
Liberia Moves to Strengthen Small Businesses with $672,000 Packaging Solution Initiative
Former Public Works Minister Ruth Coker Collins Defends Zwedru Corridor Strategy, Calls for “Facts Over Rhetoric” in Infrastructure Debate
Power Shake-Up in CDC Diaspora: NEC Dissolves CDC-USA Interim Leadership, Appoints Rev. Muin to Lead U.S. Chapter
Tweah Reemerges, Sparks Fierce National Debate Over Governance and Liberia’s Future
Koijee Slams Tribal Politics and Political Manipulation in Lofa, Accuses Boakai Government
Mali and Burkina Faso Impose Reciprocal Travel Ban on U.S. Citizens
Guinea Election: A Turn Toward Continuity Amid Questions of Democratic Credibility
Liberia’s Revenue Grows Stronger as LRA Exceeds National Target for Second Year
Liberia’s Gender Legal Landscape: Laws in Place, Systems Lag Far Behind
Liberia’s Economy Gains Momentum as CBL Governor Highlights Progress at ECOWAS Meeting
Liberia Positions Itself for Eco Currency Entry as Economy Expands by 5.1%
From Overcrowded Classrooms to Renewal: NPA Mobilizes $1 Million to Transform UL
Ex-Public Works Minister Ruth Coker Collins Slams Ministry’s “Lack of Capacity,” Defends Her Infrastructure Record
CDC Expels Three Senior Members, Citing Alleged Alignment With Government and Undermining Party Ideology
UL to Celebrate 75 Years of Excellence
Saudi Fund Advances Liberia’s Push for Major Infrastructure Development
West African Financial Leaders Convene in Monrovia for ECOWAS Statutory Meetings
Unlocking Liberia’s Potential: Government Launches Major Revenue and Transparency Reforms
Incremental Gains, Enduring Impunity: Liberia’s CPI Improvement Fails the Test of Reform
Boakai’s Call for Unity Faces Scrutiny as Opposition, Citizens Question Actions Behind the Words
REMAPSEN Liberia Set to Strengthen Health and Environmental Reporting
Energy Sector Women Step Into the Spotlight at Liberia’s International Women’s Day Celebration
FeJAL Concludes Three-Day Retreat, Honors Outstanding Women Journalists
Dr. Rasha Kelej Named Among Africa’s 100 Most Influential Leaders
LAVA Honored for Strengthening Liberia–U.S. Ties at 2025 Military Ball