Edit File by line
/home/zeestwma/ajeebong.../wp-conte.../plugins/litespee.../src
File: vpi.cls.php
<?php
[0] Fix | Delete
/**
[1] Fix | Delete
* The viewport image (VPI) class.
[2] Fix | Delete
*
[3] Fix | Delete
* Handles discovering above-the-fold images for posts/pages and stores the
[4] Fix | Delete
* viewport image list per post (desktop & mobile). Coordinates with the
[5] Fix | Delete
* remote service via queue + cron + webhook notify.
[6] Fix | Delete
*
[7] Fix | Delete
* @since 4.7
[8] Fix | Delete
* @package LiteSpeed
[9] Fix | Delete
*/
[10] Fix | Delete
[11] Fix | Delete
namespace LiteSpeed;
[12] Fix | Delete
[13] Fix | Delete
defined( 'WPINC' ) || exit();
[14] Fix | Delete
[15] Fix | Delete
/**
[16] Fix | Delete
* Generate and manage ViewPort Images (VPI) for pages.
[17] Fix | Delete
*/
[18] Fix | Delete
class VPI extends Base {
[19] Fix | Delete
[20] Fix | Delete
/**
[21] Fix | Delete
* Log tag for debug output.
[22] Fix | Delete
*
[23] Fix | Delete
* @var string
[24] Fix | Delete
*/
[25] Fix | Delete
const LOG_TAG = '[VPI]';
[26] Fix | Delete
[27] Fix | Delete
/**
[28] Fix | Delete
* Action types.
[29] Fix | Delete
*
[30] Fix | Delete
* @var string
[31] Fix | Delete
*/
[32] Fix | Delete
const TYPE_GEN = 'gen';
[33] Fix | Delete
const TYPE_CLEAR_Q = 'clear_q';
[34] Fix | Delete
[35] Fix | Delete
/**
[36] Fix | Delete
* VPI Desktop Meta name.
[37] Fix | Delete
*
[38] Fix | Delete
* @since 7.6
[39] Fix | Delete
* @var string
[40] Fix | Delete
*/
[41] Fix | Delete
const POST_META = 'litespeed_vpi_list';
[42] Fix | Delete
/**
[43] Fix | Delete
* VPI Mobile Meta name.
[44] Fix | Delete
*
[45] Fix | Delete
* @since 7.6
[46] Fix | Delete
* @var string
[47] Fix | Delete
*/
[48] Fix | Delete
const POST_META_MOBILE = 'litespeed_vpi_list_mobile';
[49] Fix | Delete
[50] Fix | Delete
/**
[51] Fix | Delete
* Summary values persisted between requests (timings, last runs, etc).
[52] Fix | Delete
*
[53] Fix | Delete
* @var array
[54] Fix | Delete
*/
[55] Fix | Delete
protected $_summary;
[56] Fix | Delete
[57] Fix | Delete
/**
[58] Fix | Delete
* In-memory working queue for VPI jobs.
[59] Fix | Delete
*
[60] Fix | Delete
* @var array
[61] Fix | Delete
*/
[62] Fix | Delete
private $_queue;
[63] Fix | Delete
[64] Fix | Delete
/**
[65] Fix | Delete
* Init.
[66] Fix | Delete
*
[67] Fix | Delete
* @since 4.7
[68] Fix | Delete
*/
[69] Fix | Delete
public function __construct() {
[70] Fix | Delete
$this->_summary = self::get_summary();
[71] Fix | Delete
}
[72] Fix | Delete
[73] Fix | Delete
/**
[74] Fix | Delete
* Queue the current page for VPI generation.
[75] Fix | Delete
*
[76] Fix | Delete
* @since 4.7
[77] Fix | Delete
* @return void
[78] Fix | Delete
*/
[79] Fix | Delete
public function add_to_queue() {
[80] Fix | Delete
$is_mobile = $this->_separate_mobile();
[81] Fix | Delete
[82] Fix | Delete
global $wp;
[83] Fix | Delete
$request_url = home_url( $wp->request );
[84] Fix | Delete
[85] Fix | Delete
if ( ! apply_filters( 'litespeed_vpi_should_queue', true, $request_url ) ) {
[86] Fix | Delete
return;
[87] Fix | Delete
}
[88] Fix | Delete
[89] Fix | Delete
// Sanitize user agent coming from the server superglobal.
[90] Fix | Delete
$ua = ! empty( $_SERVER['HTTP_USER_AGENT'] )
[91] Fix | Delete
? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) )
[92] Fix | Delete
: '';
[93] Fix | Delete
[94] Fix | Delete
// Store it to prepare for cron.
[95] Fix | Delete
$this->_queue = $this->load_queue( 'vpi' );
[96] Fix | Delete
[97] Fix | Delete
if ( count( $this->_queue ) > 500 ) {
[98] Fix | Delete
self::debug( 'Queue is full - 500' );
[99] Fix | Delete
return;
[100] Fix | Delete
}
[101] Fix | Delete
[102] Fix | Delete
$home_id = (int) get_option( 'page_for_posts' );
[103] Fix | Delete
[104] Fix | Delete
if ( ! is_singular() && ! ( $home_id > 0 && is_home() ) ) {
[105] Fix | Delete
self::debug( 'not single post ID' );
[106] Fix | Delete
return;
[107] Fix | Delete
}
[108] Fix | Delete
[109] Fix | Delete
$post_id = is_home() ? $home_id : get_the_ID();
[110] Fix | Delete
[111] Fix | Delete
$queue_k = ( $is_mobile ? 'mobile' : '' ) . ' ' . $request_url;
[112] Fix | Delete
if ( ! empty( $this->_queue[ $queue_k ] ) ) {
[113] Fix | Delete
self::debug( 'queue k existed ' . $queue_k );
[114] Fix | Delete
return;
[115] Fix | Delete
}
[116] Fix | Delete
[117] Fix | Delete
$this->_queue[ $queue_k ] = [
[118] Fix | Delete
'url' => apply_filters( 'litespeed_vpi_url', $request_url ),
[119] Fix | Delete
'post_id' => $post_id,
[120] Fix | Delete
'user_agent' => substr( $ua, 0, 200 ),
[121] Fix | Delete
'is_mobile' => $is_mobile,
[122] Fix | Delete
]; // Current UA will be used to request.
[123] Fix | Delete
$this->save_queue( 'vpi', $this->_queue );
[124] Fix | Delete
self::debug( 'Added queue_vpi [url] ' . $queue_k . ' [UA] ' . $ua );
[125] Fix | Delete
[126] Fix | Delete
// Prepare cache tag for later purge.
[127] Fix | Delete
Tag::add( 'VPI.' . md5( $queue_k ) );
[128] Fix | Delete
}
[129] Fix | Delete
[130] Fix | Delete
/**
[131] Fix | Delete
* Handle finish notifications from remote service.
[132] Fix | Delete
*
[133] Fix | Delete
* Expects JSON body; falls back to $_POST for legacy callers.
[134] Fix | Delete
*
[135] Fix | Delete
* @since 4.7
[136] Fix | Delete
* @return array Response object for the cloud layer.
[137] Fix | Delete
*/
[138] Fix | Delete
public function notify() {
[139] Fix | Delete
// phpcs:ignore WordPress.Security.NonceVerification.Missing
[140] Fix | Delete
$post_data = \json_decode( file_get_contents( 'php://input' ), true );
[141] Fix | Delete
if ( is_null( $post_data ) ) {
[142] Fix | Delete
// phpcs:ignore WordPress.Security.NonceVerification.Missing
[143] Fix | Delete
$post_data = $_POST;
[144] Fix | Delete
}
[145] Fix | Delete
self::debug( 'notify() data', $post_data );
[146] Fix | Delete
[147] Fix | Delete
$this->_queue = $this->load_queue( 'vpi' );
[148] Fix | Delete
[149] Fix | Delete
list( $post_data ) = $this->cls( 'Cloud' )->extract_msg( $post_data, 'vpi' );
[150] Fix | Delete
[151] Fix | Delete
$notified_data = $post_data['data'];
[152] Fix | Delete
if ( empty( $notified_data ) || ! is_array( $notified_data ) ) {
[153] Fix | Delete
self::debug( '❌ notify exit: no notified data' );
[154] Fix | Delete
return Cloud::err( 'no notified data' );
[155] Fix | Delete
}
[156] Fix | Delete
[157] Fix | Delete
// Check if it's in queue or not.
[158] Fix | Delete
$valid_i = 0;
[159] Fix | Delete
foreach ( $notified_data as $v ) {
[160] Fix | Delete
if ( empty( $v['request_url'] ) ) {
[161] Fix | Delete
self::debug( '❌ notify bypass: no request_url', $v );
[162] Fix | Delete
continue;
[163] Fix | Delete
}
[164] Fix | Delete
if ( empty( $v['queue_k'] ) ) {
[165] Fix | Delete
self::debug( '❌ notify bypass: no queue_k', $v );
[166] Fix | Delete
continue;
[167] Fix | Delete
}
[168] Fix | Delete
[169] Fix | Delete
$queue_k = $v['queue_k'];
[170] Fix | Delete
[171] Fix | Delete
if ( empty( $this->_queue[ $queue_k ] ) ) {
[172] Fix | Delete
self::debug( '❌ notify bypass: no this queue [q_k]' . $queue_k );
[173] Fix | Delete
continue;
[174] Fix | Delete
}
[175] Fix | Delete
[176] Fix | Delete
// Save data.
[177] Fix | Delete
if ( ! empty( $v['data_vpi'] ) ) {
[178] Fix | Delete
$post_id = (int) $this->_queue[ $queue_k ]['post_id'];
[179] Fix | Delete
$name = ! empty( $v['is_mobile'] ) ? self::POST_META_MOBILE : self::POST_META;
[180] Fix | Delete
$urldecode = is_array( $v['data_vpi'] ) ? array_map( 'urldecode', $v['data_vpi'] ) : urldecode( $v['data_vpi'] );
[181] Fix | Delete
self::debug( 'save data_vpi', $urldecode );
[182] Fix | Delete
$this->cls( 'Metabox' )->save( $post_id, $name, $urldecode );
[183] Fix | Delete
[184] Fix | Delete
++$valid_i;
[185] Fix | Delete
}
[186] Fix | Delete
[187] Fix | Delete
unset( $this->_queue[ $queue_k ] );
[188] Fix | Delete
self::debug( 'notify data handled, unset queue [q_k] ' . $queue_k );
[189] Fix | Delete
}
[190] Fix | Delete
$this->save_queue( 'vpi', $this->_queue );
[191] Fix | Delete
[192] Fix | Delete
self::debug( 'notified' );
[193] Fix | Delete
[194] Fix | Delete
return Cloud::ok( [ 'count' => $valid_i ] );
[195] Fix | Delete
}
[196] Fix | Delete
[197] Fix | Delete
/**
[198] Fix | Delete
* Cron entry point.
[199] Fix | Delete
*
[200] Fix | Delete
* @since 4.7
[201] Fix | Delete
*
[202] Fix | Delete
* @param bool $do_continue Continue processing multiple queue items within one cron tick.
[203] Fix | Delete
* @return mixed Result of the handler.
[204] Fix | Delete
*/
[205] Fix | Delete
public static function cron( $do_continue = false ) {
[206] Fix | Delete
$_instance = self::cls();
[207] Fix | Delete
return $_instance->_cron_handler( $do_continue );
[208] Fix | Delete
}
[209] Fix | Delete
[210] Fix | Delete
/**
[211] Fix | Delete
* Cron queue processor.
[212] Fix | Delete
*
[213] Fix | Delete
* @since 4.7
[214] Fix | Delete
*
[215] Fix | Delete
* @param bool $do_continue Continue processing multiple queue items within one cron tick.
[216] Fix | Delete
* @return void
[217] Fix | Delete
*/
[218] Fix | Delete
private function _cron_handler( $do_continue = false ) {
[219] Fix | Delete
self::debug( 'cron start' );
[220] Fix | Delete
$this->_queue = $this->load_queue( 'vpi' );
[221] Fix | Delete
[222] Fix | Delete
if ( empty( $this->_queue ) ) {
[223] Fix | Delete
return;
[224] Fix | Delete
}
[225] Fix | Delete
[226] Fix | Delete
// For cron, need to check request interval too.
[227] Fix | Delete
if ( ! $do_continue ) {
[228] Fix | Delete
if ( ! empty( $this->_summary['curr_request_vpi'] ) && time() - (int) $this->_summary['curr_request_vpi'] < 300 && ! $this->conf( self::O_DEBUG ) ) {
[229] Fix | Delete
self::debug( 'Last request not done' );
[230] Fix | Delete
return;
[231] Fix | Delete
}
[232] Fix | Delete
}
[233] Fix | Delete
[234] Fix | Delete
$i = 0;
[235] Fix | Delete
foreach ( $this->_queue as $k => $v ) {
[236] Fix | Delete
if ( ! empty( $v['_status'] ) ) {
[237] Fix | Delete
continue;
[238] Fix | Delete
}
[239] Fix | Delete
[240] Fix | Delete
self::debug( 'cron job [tag] ' . $k . ' [url] ' . $v['url'] . ( $v['is_mobile'] ? ' 📱 ' : '' ) . ' [UA] ' . $v['user_agent'] );
[241] Fix | Delete
[242] Fix | Delete
++$i;
[243] Fix | Delete
$res = $this->_send_req( $v['url'], $k, $v['user_agent'], $v['is_mobile'] );
[244] Fix | Delete
if ( ! $res ) {
[245] Fix | Delete
// Status is wrong, drop this item from queue.
[246] Fix | Delete
$this->_queue = $this->load_queue( 'vpi' );
[247] Fix | Delete
unset( $this->_queue[ $k ] );
[248] Fix | Delete
$this->save_queue( 'vpi', $this->_queue );
[249] Fix | Delete
[250] Fix | Delete
if ( ! $do_continue ) {
[251] Fix | Delete
return;
[252] Fix | Delete
}
[253] Fix | Delete
[254] Fix | Delete
GUI::print_loading( count( $this->_queue ), 'VPI' );
[255] Fix | Delete
Router::self_redirect( Router::ACTION_VPI, self::TYPE_GEN );
[256] Fix | Delete
return;
[257] Fix | Delete
}
[258] Fix | Delete
[259] Fix | Delete
// Exit queue if out of quota or service is hot.
[260] Fix | Delete
if ( 'out_of_quota' === $res || 'svc_hot' === $res ) {
[261] Fix | Delete
return;
[262] Fix | Delete
}
[263] Fix | Delete
[264] Fix | Delete
$this->_queue = $this->load_queue( 'vpi' );
[265] Fix | Delete
$this->_queue[ $k ]['_status'] = 'requested';
[266] Fix | Delete
$this->save_queue( 'vpi', $this->_queue );
[267] Fix | Delete
self::debug( 'Saved to queue [k] ' . $k );
[268] Fix | Delete
[269] Fix | Delete
// only request first one if not continuing.
[270] Fix | Delete
if ( ! $do_continue ) {
[271] Fix | Delete
return;
[272] Fix | Delete
}
[273] Fix | Delete
[274] Fix | Delete
GUI::print_loading( count( $this->_queue ), 'VPI' );
[275] Fix | Delete
Router::self_redirect( Router::ACTION_VPI, self::TYPE_GEN );
[276] Fix | Delete
return;
[277] Fix | Delete
}
[278] Fix | Delete
}
[279] Fix | Delete
[280] Fix | Delete
/**
[281] Fix | Delete
* Send request to QUIC.cloud API to generate VPI.
[282] Fix | Delete
*
[283] Fix | Delete
* @since 4.7
[284] Fix | Delete
* @access private
[285] Fix | Delete
*
[286] Fix | Delete
* @param string $request_url The URL to analyze for VPI.
[287] Fix | Delete
* @param string $queue_k Queue key for this job.
[288] Fix | Delete
* @param string $user_agent Sanitized User-Agent string (<=200 chars).
[289] Fix | Delete
* @param bool $is_mobile Whether the job is for mobile viewport.
[290] Fix | Delete
* @return bool|string True on queued successfully, 'out_of_quota'/'svc_hot' on throttling, or false on error.
[291] Fix | Delete
*/
[292] Fix | Delete
private function _send_req( $request_url, $queue_k, $user_agent, $is_mobile ) {
[293] Fix | Delete
$svc = Cloud::SVC_VPI;
[294] Fix | Delete
[295] Fix | Delete
// Check if has credit to push or not.
[296] Fix | Delete
$err = false;
[297] Fix | Delete
$allowance = $this->cls( 'Cloud' )->allowance( $svc, $err );
[298] Fix | Delete
if ( ! $allowance ) {
[299] Fix | Delete
self::debug( '❌ No credit: ' . $err );
[300] Fix | Delete
$err && Admin_Display::error( Error::msg( $err ) );
[301] Fix | Delete
return 'out_of_quota';
[302] Fix | Delete
}
[303] Fix | Delete
[304] Fix | Delete
set_time_limit( 120 );
[305] Fix | Delete
[306] Fix | Delete
// Update request status.
[307] Fix | Delete
self::save_summary( [ 'curr_request_vpi' => time() ], true );
[308] Fix | Delete
[309] Fix | Delete
// Gather guest HTML to send.
[310] Fix | Delete
$html = $this->cls( 'CSS' )->prepare_html( $request_url, $user_agent );
[311] Fix | Delete
[312] Fix | Delete
if ( ! $html ) {
[313] Fix | Delete
return false;
[314] Fix | Delete
}
[315] Fix | Delete
[316] Fix | Delete
// Parse HTML to gather CSS content before requesting.
[317] Fix | Delete
$css = false;
[318] Fix | Delete
list( $css, $html ) = $this->cls( 'CSS' )->prepare_css( $html );
[319] Fix | Delete
[320] Fix | Delete
if ( ! $css ) {
[321] Fix | Delete
self::debug( '❌ No css' );
[322] Fix | Delete
return false;
[323] Fix | Delete
}
[324] Fix | Delete
[325] Fix | Delete
$data = [
[326] Fix | Delete
'url' => $request_url,
[327] Fix | Delete
'queue_k' => $queue_k,
[328] Fix | Delete
'user_agent' => $user_agent,
[329] Fix | Delete
'is_mobile' => $is_mobile ? 1 : 0, // todo: compatible w/ tablet.
[330] Fix | Delete
'html' => $html,
[331] Fix | Delete
'css' => $css,
[332] Fix | Delete
];
[333] Fix | Delete
self::debug( 'Generating: ', $data );
[334] Fix | Delete
[335] Fix | Delete
$json = Cloud::post( $svc, $data, 30 );
[336] Fix | Delete
if ( ! is_array( $json ) ) {
[337] Fix | Delete
return $json;
[338] Fix | Delete
}
[339] Fix | Delete
[340] Fix | Delete
// Unknown status, remove this line.
[341] Fix | Delete
if ( 'queued' !== $json['status'] ) {
[342] Fix | Delete
return false;
[343] Fix | Delete
}
[344] Fix | Delete
[345] Fix | Delete
// Save summary data.
[346] Fix | Delete
self::reload_summary();
[347] Fix | Delete
$this->_summary['last_spent_vpi'] = time() - (int) $this->_summary['curr_request_vpi'];
[348] Fix | Delete
$this->_summary['last_request_vpi'] = $this->_summary['curr_request_vpi'];
[349] Fix | Delete
$this->_summary['curr_request_vpi'] = 0;
[350] Fix | Delete
self::save_summary();
[351] Fix | Delete
[352] Fix | Delete
return true;
[353] Fix | Delete
}
[354] Fix | Delete
[355] Fix | Delete
/**
[356] Fix | Delete
* Handle all request actions from main controller.
[357] Fix | Delete
*
[358] Fix | Delete
* @since 4.7
[359] Fix | Delete
* @return void
[360] Fix | Delete
*/
[361] Fix | Delete
public function handler() {
[362] Fix | Delete
$type = Router::verify_type();
[363] Fix | Delete
[364] Fix | Delete
switch ( $type ) {
[365] Fix | Delete
case self::TYPE_GEN:
[366] Fix | Delete
self::cron( true );
[367] Fix | Delete
break;
[368] Fix | Delete
[369] Fix | Delete
case self::TYPE_CLEAR_Q:
[370] Fix | Delete
$this->clear_q( 'vpi' );
[371] Fix | Delete
break;
[372] Fix | Delete
[373] Fix | Delete
default:
[374] Fix | Delete
break;
[375] Fix | Delete
}
[376] Fix | Delete
[377] Fix | Delete
Admin::redirect();
[378] Fix | Delete
}
[379] Fix | Delete
}
[380] Fix | Delete
[381] Fix | Delete
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function