PK
qhYξΆJίF ίF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Notice: ob_end_clean(): Failed to delete buffer. No buffer to delete in /home/telusvwg/public_html/da754d/index.php on line 8
| Dir : /home/telusvwg/hubnotchsolutions.com/wp-content/plugins/litespeed-cache/src/ |
| Server: Linux premium279.web-hosting.com 4.18.0-553.45.1.lve.el8.x86_64 #1 SMP Wed Mar 26 12:08:09 UTC 2025 x86_64 IP: 66.29.132.192 |
| Dir : /home/telusvwg/hubnotchsolutions.com/wp-content/plugins/litespeed-cache/src/img-optm-pull.trait.php |
<?php
/**
* Image optimization pull trait
*
* @package LiteSpeed
* @since 7.8
*/
namespace LiteSpeed;
use WpOrg\Requests\Autoload;
use WpOrg\Requests\Requests;
defined( 'WPINC' ) || exit();
/**
* Trait Img_Optm_Pull
*
* Handles image optimization pull and notification.
*/
trait Img_Optm_Pull {
/**
* Cloud server notify Client img status changed
*
* @access public
* @return array Response array.
*/
public function notify_img() {
// Interval validation to avoid hacking domain_key
if ( ! empty( $this->_summary['notify_ts_err'] ) && time() - $this->_summary['notify_ts_err'] < 3 ) {
return Cloud::err( 'too_often' );
}
$post_data = \json_decode( file_get_contents( 'php://input' ), true );
if ( is_null( $post_data ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$post_data = $_POST;
}
global $wpdb;
$notified_data = $post_data['data'];
if ( empty( $notified_data ) || ! is_array( $notified_data ) ) {
self::debug( 'β notify exit: no notified data' );
return Cloud::err( 'no notified data' );
}
if ( empty( $post_data['server'] ) || ( substr( $post_data['server'], -11 ) !== '.quic.cloud' && substr( $post_data['server'], -15 ) !== '.quicserver.com' ) ) {
self::debug( 'notify exit: no/wrong server' );
return Cloud::err( 'no/wrong server' );
}
if ( empty( $post_data['status'] ) ) {
self::debug( 'notify missing status' );
return Cloud::err( 'no status' );
}
$status = $post_data['status'];
self::debug( 'notified status=' . $status );
$last_log_pid = 0;
if ( empty( $this->_summary['reduced'] ) ) {
$this->_summary['reduced'] = 0;
}
if ( self::STATUS_NOTIFIED === $status ) {
// Notified data format: [ img_optm_id => [ id=>, src_size=>, ori=>, ori_md5=>, ori_reduced=>, webp=>, webp_md5=>, webp_reduced=> ] ]
$q =
"SELECT a.*, b.meta_id as b_meta_id, b.meta_value AS b_optm_info
FROM `$this->_table_img_optming` a
LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.post_id AND b.meta_key = %s
WHERE a.id IN ( " .
implode( ',', array_fill( 0, count( $notified_data ), '%d' ) ) .
' )';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $wpdb->prepare( $q, array_merge( [ self::DB_SIZE ], array_keys( $notified_data ) ) ) );
$ls_optm_size_row_exists_postids = [];
foreach ( $list as $v ) {
$json = $notified_data[ $v->id ];
// self::debug('Notified data for [id] ' . $v->id, $json);
$server = ! empty( $json['server'] ) ? $json['server'] : $post_data['server'];
$server_info = [
'server' => $server,
];
// Save server side ID to send taken notification after pulled
$server_info['id'] = $json['id'];
if ( ! empty( $json['file_id'] ) ) {
$server_info['file_id'] = $json['file_id'];
}
// Optm info array
$postmeta_info = [
'ori_total' => 0,
'ori_saved' => 0,
'webp_total' => 0,
'webp_saved' => 0,
'avif_total' => 0,
'avif_saved' => 0,
];
// Init postmeta_info for the first one
if ( ! empty( $v->b_meta_id ) ) {
foreach ( maybe_unserialize( $v->b_optm_info ) as $k2 => $v2 ) {
$postmeta_info[ $k2 ] += $v2;
}
}
if ( ! empty( $json['ori'] ) ) {
$server_info['ori_md5'] = $json['ori_md5'];
$server_info['ori'] = $json['ori'];
// Append meta info
$postmeta_info['ori_total'] += $json['src_size'];
$postmeta_info['ori_saved'] += $json['ori_reduced']; // optimized image size info in img_optm tb will be updated when pull
$this->_summary['reduced'] += $json['ori_reduced'];
}
if ( ! empty( $json['webp'] ) ) {
$server_info['webp_md5'] = $json['webp_md5'];
$server_info['webp'] = $json['webp'];
// Append meta info
$postmeta_info['webp_total'] += $json['src_size'];
$postmeta_info['webp_saved'] += $json['webp_reduced'];
$this->_summary['reduced'] += $json['webp_reduced'];
}
if ( ! empty( $json['avif'] ) ) {
$server_info['avif_md5'] = $json['avif_md5'];
$server_info['avif'] = $json['avif'];
// Append meta info
$postmeta_info['avif_total'] += $json['src_size'];
$postmeta_info['avif_saved'] += $json['avif_reduced'];
$this->_summary['reduced'] += $json['avif_reduced'];
}
// Update status and data in working table
$q = "UPDATE `$this->_table_img_optming` SET optm_status = %d, server_info = %s WHERE id = %d ";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, [ $status, wp_json_encode( $server_info ), $v->id ] ) );
// Update postmeta for optm summary
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
$postmeta_info = serialize( $postmeta_info );
if ( empty( $v->b_meta_id ) && ! in_array( $v->post_id, $ls_optm_size_row_exists_postids, true ) ) {
self::debug( 'New size info [pid] ' . $v->post_id );
$q = "INSERT INTO `$wpdb->postmeta` ( post_id, meta_key, meta_value ) VALUES ( %d, %s, %s )";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, [ $v->post_id, self::DB_SIZE, $postmeta_info ] ) );
$ls_optm_size_row_exists_postids[] = $v->post_id;
} else {
$q = "UPDATE `$wpdb->postmeta` SET meta_value = %s WHERE meta_id = %d ";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, [ $postmeta_info, $v->b_meta_id ] ) );
}
// write log
$pid_log = $last_log_pid === $v->post_id ? '.' : $v->post_id;
self::debug( 'notify_img [status] ' . $status . " \t\t[pid] " . $pid_log . " \t\t[id] " . $v->id );
$last_log_pid = $v->post_id;
}
self::save_summary();
// Mark need_pull tag for cron
self::update_option( self::DB_NEED_PULL, self::STATUS_NOTIFIED );
} else {
// Other errors will directly remove the working records
// Delete from working table
$q = "DELETE FROM `$this->_table_img_optming` WHERE id IN ( " . implode( ',', array_fill( 0, count( $notified_data ), '%d' ) ) . ' ) ';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, $notified_data ) );
}
return Cloud::ok( [ 'count' => count( $notified_data ) ] );
}
/**
* Cron start async req
*
* @since 5.5
*/
public static function start_async_cron() {
Task::async_call( 'imgoptm' );
}
/**
* Manually start async req
*
* @since 5.5
*/
public static function start_async() {
Task::async_call( 'imgoptm_force' );
$msg = __( 'Started async image optimization request', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Check if need to pull or not
*
* @since 7.2
* @return bool True if need to pull.
*/
public static function need_pull() {
$tag = (int) self::get_option( self::DB_NEED_PULL );
if ( ! $tag || self::STATUS_NOTIFIED !== $tag ) {
return false;
}
return true;
}
/**
* Ajax req handler
*
* @since 5.5
* @param bool $force Whether to force pull.
*/
public static function async_handler( $force = false ) {
self::debug( '------------async-------------start_async_handler' );
if ( ! self::need_pull() ) {
self::debug( 'β no need pull' );
return;
}
if ( defined( 'LITESPEED_IMG_OPTM_PULL_CRON' ) && ! constant( 'LITESPEED_IMG_OPTM_PULL_CRON' ) ) {
self::debug( 'Cron disabled [define] LITESPEED_IMG_OPTM_PULL_CRON' );
return;
}
self::cls()->pull( $force );
}
/**
* Calculate pull threads
*
* @since 5.8
* @access private
* @return int Number of images per request.
*/
private function _calc_pull_threads() {
global $wpdb;
if ( defined( 'LITESPEED_IMG_OPTM_PULL_THREADS' ) ) {
return constant( 'LITESPEED_IMG_OPTM_PULL_THREADS' );
}
// Tune number of images per request based on number of images waiting and cloud packages
$imgs_per_req = 1; // base 1, ramp up to ~50 max
// Ramp up the request rate based on how many images are waiting
$c = "SELECT count(id) FROM `$this->_table_img_optming` WHERE optm_status = %d";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$_c = $wpdb->prepare( $c, [ self::STATUS_NOTIFIED ] );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$images_waiting = $wpdb->get_var( $_c );
if ( $images_waiting && $images_waiting > 0 ) {
$imgs_per_req = ceil( $images_waiting / 1000 ); // ie. download 5/request if 5000 images are waiting
}
// Cap the request rate at 50 images per request
$imgs_per_req = min( 50, $imgs_per_req );
self::debug( 'Pulling images at rate: ' . $imgs_per_req . ' Images per request.' );
return $imgs_per_req;
}
/**
* Pull optimized img
*
* @since 1.6
* @access public
* @param bool $manual Whether this is a manual pull.
*/
public function pull( $manual = false ) {
global $wpdb;
$timeout_limit = ini_get( 'max_execution_time' );
$endts = time() + $timeout_limit;
self::debug( '' . ( $manual ? 'Manually' : 'Cron' ) . ' pull started [timeout: ' . $timeout_limit . 's]' );
if ( $this->cron_running() ) {
self::debug( 'Pull cron is running' );
$msg = __( 'Pull Cron is running', 'litespeed-cache' );
Admin_Display::note( $msg );
return;
}
$this->_summary['last_pulled'] = time();
$this->_summary['last_pulled_by_cron'] = ! $manual;
self::save_summary();
$imgs_per_req = $this->_calc_pull_threads();
$q = "SELECT * FROM `$this->_table_img_optming` WHERE optm_status = %d ORDER BY id LIMIT %d";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$_q = $wpdb->prepare( $q, [ self::STATUS_NOTIFIED, $imgs_per_req ] );
$rm_ori_bkup = $this->conf( self::O_IMG_OPTM_RM_BKUP );
$total_pulled_ori = 0;
$total_pulled_webp = 0;
$total_pulled_avif = 0;
$server_list = [];
try {
// phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition, WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
while ( $img_rows = $wpdb->get_results( $_q ) ) {
self::debug( 'timeout left: ' . ( $endts - time() ) . 's' );
if ( function_exists( 'set_time_limit' ) ) {
$endts += 600;
self::debug( 'Endtime extended to ' . gmdate( 'Ymd H:i:s', $endts ) );
set_time_limit( 600 ); // This will be no more important as we use noabort now
}
// Disabled as we use noabort
// if ($endts - time() < 10) {
// self::debug("π¨ End loop due to timeout limit reached " . $timeout_limit . "s");
// break;
// }
/**
* Update cron timestamp to avoid duplicated running
*
* @since 1.6.2
*/
$this->_update_cron_running();
// Run requests in parallel
$requests = []; // store each request URL for Requests::request_multiple()
$imgs_by_req = []; // store original request data so that we can reference it in the response
$req_counter = 0;
foreach ( $img_rows as $row_img ) {
// request original image
$server_info = \json_decode( $row_img->server_info, true );
if ( ! empty( $server_info['ori'] ) ) {
$image_url = $server_info['server'] . '/' . $server_info['ori'];
self::debug( 'Queueing pull: ' . $image_url );
$requests[ $req_counter ] = [
'url' => $image_url,
'type' => 'GET',
];
$imgs_by_req[ $req_counter++ ] = [
'type' => 'ori',
'data' => $row_img,
];
}
// request webp image
$webp_size = 0;
if ( ! empty( $server_info['webp'] ) ) {
$image_url = $server_info['server'] . '/' . $server_info['webp'];
self::debug( 'Queueing pull WebP: ' . $image_url );
$requests[ $req_counter ] = [
'url' => $image_url,
'type' => 'GET',
];
$imgs_by_req[ $req_counter++ ] = [
'type' => 'webp',
'data' => $row_img,
];
}
// request avif image
$avif_size = 0;
if ( ! empty( $server_info['avif'] ) ) {
$image_url = $server_info['server'] . '/' . $server_info['avif'];
self::debug( 'Queueing pull AVIF: ' . $image_url );
$requests[ $req_counter ] = [
'url' => $image_url,
'type' => 'GET',
];
$imgs_by_req[ $req_counter++ ] = [
'type' => 'avif',
'data' => $row_img,
];
}
}
self::debug( 'Loaded images count: ' . $req_counter );
$complete_action = function ( $response, $req_count ) use ( $imgs_by_req, $rm_ori_bkup, &$total_pulled_ori, &$total_pulled_webp, &$total_pulled_avif, &$server_list ) {
global $wpdb;
$row_data = isset( $imgs_by_req[ $req_count ] ) ? $imgs_by_req[ $req_count ] : false;
if ( false === $row_data ) {
self::debug( 'β failed to pull image: Request not found in lookup variable.' );
return;
}
$row_type = isset( $row_data['type'] ) ? $row_data['type'] : 'ori';
$row_img = $row_data['data'];
$local_file = $this->wp_upload_dir['basedir'] . '/' . $row_img->src;
$server_info = \json_decode( $row_img->server_info, true );
// Handle status_code 404/5xx too as its success=true
if ( empty( $response->success ) || empty( $response->status_code ) || 200 !== $response->status_code ) {
self::debug( 'β Failed to pull optimized img: HTTP error [status_code] ' . ( empty( $response->status_code ) ? 'N/A' : $response->status_code ) );
$this->_step_back_image( $row_img->id );
$msg = __( 'Some optimized image file(s) has expired and was cleared.', 'litespeed-cache' );
Admin_Display::error( $msg );
return;
}
if ( 'webp' === $row_type ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
file_put_contents( $local_file . '.webp', $response->body );
if ( ! file_exists( $local_file . '.webp' ) || ! filesize( $local_file . '.webp' ) || md5_file( $local_file . '.webp' ) !== $server_info['webp_md5'] ) {
self::debug( 'β Failed to pull optimized webp img: file md5 mismatch, server md5: ' . $server_info['webp_md5'] );
// Delete working table
$q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d ";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, $row_img->id ) );
$msg = __( 'Pulled WebP image md5 does not match the notified WebP image md5.', 'litespeed-cache' );
Admin_Display::error( $msg );
return;
}
self::debug( 'Pulled optimized img WebP: ' . $local_file . '.webp' );
$webp_size = filesize( $local_file . '.webp' );
/**
* API for WebP
*
* @since 2.9.5
* @since 3.0 $row_img less elements (see above one)
* @see #751737 - API docs for WEBP generation
*/
do_action( 'litespeed_img_pull_webp', $row_img, $local_file . '.webp' );
++$total_pulled_webp;
} elseif ( 'avif' === $row_type ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
file_put_contents( $local_file . '.avif', $response->body );
if ( ! file_exists( $local_file . '.avif' ) || ! filesize( $local_file . '.avif' ) || md5_file( $local_file . '.avif' ) !== $server_info['avif_md5'] ) {
self::debug( 'β Failed to pull optimized avif img: file md5 mismatch, server md5: ' . $server_info['avif_md5'] );
// Delete working table
$q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d ";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, $row_img->id ) );
$msg = __( 'Pulled AVIF image md5 does not match the notified AVIF image md5.', 'litespeed-cache' );
Admin_Display::error( $msg );
return;
}
self::debug( 'Pulled optimized img AVIF: ' . $local_file . '.avif' );
$avif_size = filesize( $local_file . '.avif' );
/**
* API for AVIF
*
* @since 7.0
*/
do_action( 'litespeed_img_pull_avif', $row_img, $local_file . '.avif' );
++$total_pulled_avif;
} else {
// "ori" image type
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
file_put_contents( $local_file . '.tmp', $response->body );
if ( ! file_exists( $local_file . '.tmp' ) || ! filesize( $local_file . '.tmp' ) || md5_file( $local_file . '.tmp' ) !== $server_info['ori_md5'] ) {
self::debug(
'β Failed to pull optimized img: file md5 mismatch [url] ' .
$server_info['server'] .
'/' .
$server_info['ori'] .
' [server_md5] ' .
$server_info['ori_md5']
);
// Delete working table
$q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d ";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, $row_img->id ) );
$msg = __( 'One or more pulled images does not match with the notified image md5', 'litespeed-cache' );
Admin_Display::error( $msg );
return;
}
// Backup ori img
if ( ! $rm_ori_bkup ) {
$extension = pathinfo( $local_file, PATHINFO_EXTENSION );
$bk_file = substr( $local_file, 0, -strlen( $extension ) ) . 'bk.' . $extension;
// phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename
file_exists( $local_file ) && rename( $local_file, $bk_file );
}
// Replace ori img
// phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename
rename( $local_file . '.tmp', $local_file );
self::debug( 'Pulled optimized img: ' . $local_file );
/**
* API Hook
*
* @since 2.9.5
* @since 3.0 $row_img has less elements now. Most useful ones are `post_id`/`src`
*/
do_action( 'litespeed_img_pull_ori', $row_img, $local_file );
self::debug2( 'Remove _table_img_optming record [id] ' . $row_img->id );
}
// Delete working table
$q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d ";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, $row_img->id ) );
// Save server_list to notify taken
if ( empty( $server_list[ $server_info['server'] ] ) ) {
$server_list[ $server_info['server'] ] = [];
}
$server_info_id = ! empty( $server_info['file_id'] ) ? $server_info['file_id'] : $server_info['id'];
$server_list[ $server_info['server'] ][] = $server_info_id;
++$total_pulled_ori;
};
$force_wp_remote_get = defined( 'LITESPEED_FORCE_WP_REMOTE_GET' ) && constant( 'LITESPEED_FORCE_WP_REMOTE_GET' );
if ( ! $force_wp_remote_get && class_exists( '\WpOrg\Requests\Requests' ) && class_exists( '\WpOrg\Requests\Autoload' ) ) {
// Make sure Requests can load internal classes.
Autoload::register();
// Run pull requests in parallel
Requests::request_multiple( $requests, [
'timeout' => 60,
'connect_timeout' => 60,
'complete' => $complete_action,
'verify' => false,
'verifyname' => false,
] );
} else {
foreach ( $requests as $cnt => $req ) {
$wp_response = wp_safe_remote_get( $req['url'], [ 'timeout' => 60 ] );
$request_response = [
'success' => false,
'status_code' => 0,
'body' => null,
'sslverify' => false,
];
if ( is_wp_error( $wp_response ) ) {
$error_message = $wp_response->get_error_message();
self::debug( 'β failed to pull image: ' . $error_message );
} else {
$request_response['success'] = true;
$request_response['status_code'] = $wp_response['response']['code'];
$request_response['body'] = $wp_response['body'];
}
self::debug( 'response code [code] ' . $wp_response['response']['code'] . ' [url] ' . $req['url'] );
$request_response = (object) $request_response;
$complete_action( $request_response, $cnt );
}
}
self::debug( 'Current batch pull finished' );
}
} catch ( \Exception $e ) {
Admin_Display::error( 'Image pull process failure: ' . $e->getMessage() );
}
// Notify IAPI images taken
foreach ( $server_list as $server => $img_list ) {
$data = [
'action' => self::CLOUD_ACTION_TAKEN,
'list' => $img_list,
'server' => $server,
];
// TODO: improve this so we do not call once per server, but just once and then filter on the server side
Cloud::post( Cloud::SVC_IMG_OPTM, $data );
}
if ( empty( $this->_summary['img_taken'] ) ) {
$this->_summary['img_taken'] = 0;
}
$this->_summary['img_taken'] += $total_pulled_ori + $total_pulled_webp + $total_pulled_avif;
self::save_summary();
// Manually running needs to roll back timestamp for next running
if ( $manual ) {
$this->_update_cron_running( true );
}
// $msg = sprintf(__('Pulled %d image(s)', 'litespeed-cache'), $total_pulled_ori + $total_pulled_webp);
// Admin_Display::success($msg);
// Check if there is still task in queue
$q = "SELECT * FROM `$this->_table_img_optming` WHERE optm_status = %d LIMIT 1";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$to_be_continued = $wpdb->get_row( $wpdb->prepare( $q, self::STATUS_NOTIFIED ) );
if ( $to_be_continued ) {
self::debug( 'Task in queue, to be continued...' );
return;
// return Router::self_redirect(Router::ACTION_IMG_OPTM, self::TYPE_PULL);
}
// If all pulled, update tag to done
self::debug( 'Marked pull status to all pulled' );
self::update_option( self::DB_NEED_PULL, self::STATUS_PULLED );
}
/**
* Push image back to previous status
*
* @since 3.0
* @access private
* @param int $id The image ID.
*/
private function _step_back_image( $id ) {
global $wpdb;
self::debug( 'Push image back to new status [id] ' . $id );
// Reset the image to gathered status
$q = "UPDATE `$this->_table_img_optming` SET optm_status = %d WHERE id = %d ";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->prepare( $q, [ self::STATUS_RAW, $id ] ) );
}
}