File "cloud.cls.php"
Full Path: /home/ycoalition/public_html/blog/wp-admin/js/widgets/plugins/litespeed-cache/src/cloud.cls.php
File size: 31.73 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Cloud service cls
*
* @since 3.0
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Cloud extends Base {
const LOG_TAG = '❄️';
const CLOUD_SERVER = 'https://api.quic.cloud';
const CLOUD_IPS = 'https://quic.cloud/ips';
const CLOUD_SERVER_DASH = 'https://my.quic.cloud';
const CLOUD_SERVER_WP = 'https://wpapi.quic.cloud';
const SVC_D_NODES = 'd/nodes';
const SVC_D_SYNC_CONF = 'd/sync_conf';
const SVC_D_USAGE = 'd/usage';
const SVC_PAGE_OPTM = 'page_optm';
const SVC_CCSS = 'ccss';
const SVC_UCSS = 'ucss';
const SVC_LQIP = 'lqip';
const SVC_IMG_OPTM = 'img_optm';
const SVC_HEALTH = 'health';
const SVC_CDN = 'cdn';
const BM_IMG_OPTM_PRIO = 16;
const BM_IMG_OPTM_JUMBO_GROUP = 32;
const IMG_OPTM_JUMBO_GROUP = 1000;
const IMG_OPTM_DEFAULT_GROUP = 200;
const IMGOPTM_TAKEN = 'img_optm-taken';
const TTL_NODE = 3; // Days before node expired
const EXPIRATION_REQ = 300; // Seconds of min interval between two unfinished requests
const EXPIRATION_TOKEN = 900; // Min intval to request a token 15m
const TTL_IPS = 3; // Days for node ip list cache
const API_REPORT = 'wp/report' ;
const API_NEWS = 'news';
const API_VER = 'ver';
const API_BETA_TEST = 'beta_test';
private static $CENTER_SVC_SET = array(
self::SVC_D_NODES,
self::SVC_D_SYNC_CONF,
self::SVC_D_USAGE,
// self::API_NEWS,
self::API_REPORT,
// self::API_VER,
// self::API_BETA_TEST,
);
private static $WP_SVC_SET = array(
self::API_NEWS,
self::API_VER,
self::API_BETA_TEST,
);
// No api key needed for these services
private static $_PUB_SVC_SET = array(
self::API_NEWS,
self::API_REPORT,
self::API_VER,
self::API_BETA_TEST,
);
public static $SERVICES_LOAD_CHECK = array(
self::SVC_CCSS,
self::SVC_UCSS,
self::SVC_LQIP,
self::SVC_HEALTH,
);
public static $SERVICES = array(
self::SVC_IMG_OPTM,
self::SVC_PAGE_OPTM,
self::SVC_CCSS,
self::SVC_UCSS,
self::SVC_LQIP,
self::SVC_CDN,
self::SVC_HEALTH,
);
const TYPE_CLEAR_PROMO = 'clear_promo';
const TYPE_REDETECT_CLOUD = 'redetect_cloud';
const TYPE_CLEAR_CLOUD = 'clear_cloud';
const TYPE_GEN_KEY = 'gen_key';
const TYPE_LINK = 'link';
const TYPE_SYNC_USAGE = 'sync_usage';
private $_api_key;
protected $_summary;
/**
* Init
*
* @since 3.0
*/
public function __construct() {
$this->_api_key = $this->conf( self::O_API_KEY );
$this->_summary = self::get_summary();
}
/**
* Show latest commit version always if is on dev
*
* @since 3.0
*/
public function check_dev_version() {
if ( ! preg_match( '/[^\d\.]/', Core::VER ) ) {
return;
}
$last_check = empty( $this->_summary[ 'last_request.' . self::API_VER ] ) ? 0 : $this->_summary[ 'last_request.' . self::API_VER ] ;
if ( time() - $last_check > 600 ) {
$auto_v = self::version_check( 'dev' );
if ( ! empty( $auto_v[ 'dev' ] ) ) {
$this->_summary[ 'version.dev' ] = $auto_v[ 'dev' ];
self::save_summary( $this->_summary );
}
}
if ( empty( $this->_summary[ 'version.dev' ] ) ) {
return;
}
self::debug( 'Latest dev version ' . $this->_summary[ 'version.dev' ] );
if ( version_compare( $this->_summary[ 'version.dev' ], Core::VER, '<=' ) ) {
return;
}
// Show the dev banner
require_once LSCWP_DIR . 'tpl/banner/new_version_dev.tpl.php';
}
/**
* Check latest version
*
* @since 2.9
* @access public
*/
public static function version_check( $src = false ) {
$req_data = array(
'v' => defined( 'LSCWP_CUR_V' ) ? LSCWP_CUR_V : '',
'src' => $src,
);
if ( defined( 'LITESPEED_ERR' ) ) {
$req_data[ 'err' ] = base64_encode( ! is_string( LITESPEED_ERR ) ? json_encode( LITESPEED_ERR ) : LITESPEED_ERR ) ;
}
$data = self::get( self::API_VER, $req_data );
return $data;
}
/**
* Show latest news
*
* @since 3.0
*/
public function news() {
$this->_update_news();
if ( empty( $this->_summary[ 'news.new' ] ) ) {
return;
}
if ( ! empty( $this->_summary[ 'news.plugin' ] ) && Activation::cls()->dash_notifier_is_plugin_active( $this->_summary[ 'news.plugin' ] ) ) {
return;
}
require_once LSCWP_DIR . 'tpl/banner/cloud_news.tpl.php' ;
}
/**
* Update latest news
*
* @since 2.9.9.1
*/
private function _update_news() {
if ( ! empty( $this->_summary[ 'news.utime' ] ) && time() - $this->_summary[ 'news.utime' ] < 86400 * 3 ) {
return;
}
$this->_summary[ 'news.utime' ] = time();
self::save_summary();
$data = self::get( self::API_NEWS );
if ( empty( $data[ 'id' ] ) ) {
return;
}
// Save news
if ( ! empty( $this->_summary[ 'news.id' ] ) && $this->_summary[ 'news.id' ] == $data[ 'id' ] ) {
return;
}
$this->_summary[ 'news.id' ] = $data[ 'id' ];
$this->_summary[ 'news.plugin' ] = ! empty( $data[ 'plugin' ] ) ? $data[ 'plugin' ] : '';
$this->_summary[ 'news.title' ] = ! empty( $data[ 'title' ] ) ? $data[ 'title' ] : '';
$this->_summary[ 'news.content' ] = ! empty( $data[ 'content' ] ) ? $data[ 'content' ] : '';
$this->_summary[ 'news.zip' ] = ! empty( $data[ 'zip' ] ) ? $data[ 'zip' ] : '';
$this->_summary[ 'news.new' ] = 1;
if ( $this->_summary[ 'news.plugin' ] ) {
$plugin_info = Activation::cls()->dash_notifier_get_plugin_info( $this->_summary[ 'news.plugin' ] );
if ( $plugin_info && ! empty( $plugin_info->name ) ) {
$this->_summary[ 'news.plugin_name' ] = $plugin_info->name;
}
}
self::save_summary();
}
/**
* Check if contains a package in a service or not
*
* @since 4.0
*/
public function has_pkg( $service, $pkg ) {
if ( ! empty( $this->_summary[ 'usage.' . $service ][ 'pkgs' ] ) && $this->_summary[ 'usage.' . $service ][ 'pkgs' ] & $pkg ) {
return true;
}
return false;
}
/**
* Get allowance of current service
*
* @since 3.0
* @access private
*/
public function allowance( $service, &$err = false ) {
// Only auto sync usage at most one time per day
if ( empty( $this->_summary[ 'last_request.' . self::SVC_D_USAGE ] ) || time() - $this->_summary[ 'last_request.' . self::SVC_D_USAGE ] > 86400 ) {
$this->sync_usage();
}
if ( in_array( $service, array( self::SVC_CCSS, self::SVC_UCSS ) ) ) { // @since 4.2
$service = self::SVC_PAGE_OPTM;
}
if ( empty( $this->_summary[ 'usage.' . $service ] ) ) {
return 0;
}
$usage = $this->_summary[ 'usage.' . $service ];
// Image optm is always free
$allowance_max = 0;
if ( $service == self::SVC_IMG_OPTM ) {
$allowance_max = self::IMG_OPTM_DEFAULT_GROUP;
if ( ! empty( $usage[ 'pkgs' ] ) && $usage[ 'pkgs' ] & self::BM_IMG_OPTM_JUMBO_GROUP ) {
$allowance_max = self::IMG_OPTM_JUMBO_GROUP;
}
}
$allowance = $usage[ 'quota' ] - $usage[ 'used' ];
$err = 'out_of_quota';
if ( $allowance > 0 ) {
if ( $allowance_max && $allowance_max < $allowance ) {
$allowance = $allowance_max;
}
// Daily limit @since 4.2
if ( isset( $usage[ 'remaining_daily_quota' ] ) && $usage[ 'remaining_daily_quota' ] >= 0 && $usage[ 'remaining_daily_quota' ] < $allowance ) {
$allowance = $usage[ 'remaining_daily_quota' ];
if ( ! $allowance ) {
$err = 'out_of_daily_quota';
}
}
return $allowance;
}
// Check Pay As You Go balance
if ( empty( $usage[ 'pag_bal' ] ) ) {
return $allowance_max;
}
if ( $allowance_max && $allowance_max < $usage[ 'pag_bal' ] ) {
return $allowance_max;
}
return $usage[ 'pag_bal' ];
}
/**
* Sync Cloud usage summary data
*
* @since 3.0
* @access public
*/
public function sync_usage() {
$usage = $this->_post( self::SVC_D_USAGE );
if ( ! $usage ) {
return;
}
self::debug( 'sync_usage ' . json_encode( $usage ) );
foreach ( self::$SERVICES as $v ) {
$this->_summary[ 'usage.' . $v ] = ! empty( $usage[ $v ] ) ? $usage[ $v ] : false;
}
self::save_summary();
return $this->_summary;
}
/**
* Clear all existing cloud nodes for future reconnect
*
* @since 3.0
* @access public
*/
public function clear_cloud() {
foreach ( self::$SERVICES as $service ) {
if ( isset( $this->_summary[ 'server.' . $service ] ) ) {
unset( $this->_summary[ 'server.' . $service ] );
}
if ( isset( $this->_summary[ 'server_date.' . $service ] ) ) {
unset( $this->_summary[ 'server_date.' . $service ] );
}
}
self::save_summary();
self::debug( 'Cleared all local service node caches' );
}
/**
* ping clouds to find the fastest node
*
* @since 3.0
* @access public
*/
public function detect_cloud( $service, $force = false ) {
if ( in_array( $service, self::$CENTER_SVC_SET ) ) {
return self::CLOUD_SERVER;
}
if ( in_array( $service, self::$WP_SVC_SET ) ) {
return self::CLOUD_SERVER_WP;
}
// Check if the stored server needs to be refreshed
if ( ! $force ) {
if ( ! empty( $this->_summary[ 'server.' . $service ] ) && ! empty( $this->_summary[ 'server_date.' . $service ] ) && $this->_summary[ 'server_date.' . $service ] > time() - 86400 * self::TTL_NODE ) {
return $this->_summary[ 'server.' . $service ];
}
}
if ( ! $service || ! in_array( $service, self::$SERVICES ) ) {
$msg = __( 'Cloud Error', 'litespeed-cache' ) . ': ' . $service;
Admin_Display::error( $msg );
return false;
}
// Send request to Quic Online Service
$json = $this->_post( self::SVC_D_NODES, array( 'svc' => $service ) );
// Check if get list correctly
if ( empty( $json[ 'list' ] ) || ! is_array( $json[ 'list' ] ) ) {
self::debug( 'request cloud list failed: ', $json );
if ( $json ) {
$msg = __( 'Cloud Error', 'litespeed-cache' ) . ": [Service] $service [Info] " . json_encode( $json );
Admin_Display::error( $msg );
}
return false;
}
// Ping closest cloud
$speed_list = array();
foreach ( $json[ 'list' ] as $v ) {
$speed_list[ $v ] = Utility::ping( $v );
}
$min = min( $speed_list );
if ( $min == 99999 ) {
self::debug( 'failed to ping all clouds' );
return false;
}
// Random pick same time range ip (230ms 250ms)
$range_len = strlen( $min );
$range_num = substr( $min, 0, 1 );
$valid_clouds = array();
foreach ($speed_list as $node => $speed ) {
if ( strlen( $speed ) == $range_len && substr( $speed, 0, 1 ) == $range_num ) {
$valid_clouds[] = $node;
}
// Append the lower speed ones
else if ( $speed < $min * 4 ) {
$valid_clouds[] = $node;
}
}
if ( ! $valid_clouds ) {
$msg = __( 'Cloud Error', 'litespeed-cache' ) . ": [Service] $service [Info] " . __( 'No available Cloud Node.', 'litespeed-cache' );
Admin_Display::error( $msg );
return false;
}
self::debug( 'Closest nodes list', $valid_clouds );
// Check server load
if ( in_array( $service, self::$SERVICES_LOAD_CHECK ) ) {
$valid_cloud_loads = array();
foreach ( $valid_clouds as $k => $v ) {
$response = wp_remote_get( $v, array( 'timeout' => 5, 'sslverify' => true ) );
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
self::debug( 'failed to do load checker: ' . $error_message );
continue;
}
$curr_load = json_decode( $response[ 'body' ], true );
if ( ! empty( $curr_load[ '_res' ] ) && $curr_load[ '_res' ] == 'ok' && isset( $curr_load[ 'load' ] ) ) {
$valid_cloud_loads[ $v ] = $curr_load[ 'load' ];
}
}
if ( ! $valid_cloud_loads ) {
$msg = __( 'Cloud Error', 'litespeed-cache' ) . ": [Service] $service [Info] " . __( 'No available Cloud Node after checked server load.', 'litespeed-cache' );
Admin_Display::error( $msg );
return false;
}
self::debug( 'Closest nodes list after load check', $valid_cloud_loads );
$qualified_list = array_keys( $valid_cloud_loads, min( $valid_cloud_loads ) );
}
else {
$qualified_list = $valid_clouds;
}
$closest = $qualified_list[ array_rand( $qualified_list ) ];
self::debug( 'Chose node: ' . $closest );
// store data into option locally
$this->_summary[ 'server.' . $service ] = $closest;
$this->_summary[ 'server_date.' . $service ] = time();
self::save_summary();
return $this->_summary[ 'server.' . $service ];
}
/**
* Get data from QUIC cloud server
*
* @since 3.0
* @access public
*/
public static function get( $service, $data = array() ) {
$instance = self::cls();
return $instance->_get( $service, $data );
}
/**
* Get data from QUIC cloud server
*
* @since 3.0
* @access private
*/
private function _get( $service, $data = false ) {
$service_tag = $service;
if ( ! empty( $data[ 'action' ] ) ) {
$service_tag .= '-' . $data[ 'action' ];
}
if ( ! $this->_maybe_cloud( $service_tag ) ) {
return;
}
$server = $this->detect_cloud( $service );
if ( ! $server ) {
return;
}
$url = $server . '/' . $service;
$param = array(
'site_url' => home_url(),
'domain_key' => $this->_api_key,
'ver' => Core::VER,
);
if ( $data ) {
$param[ 'data' ] = $data;
}
$url .= '?' . http_build_query( $param );
self::debug( 'getting from : ' . $url );
$this->_summary[ 'curr_request.' . $service_tag ] = time();
self::save_summary();
$response = wp_remote_get( $url, array( 'timeout' => 15, 'sslverify' => true ) );
return $this->_parse_response( $response, $service, $service_tag, $server );
}
/**
* Check if is able to do cloud request or not
*
* @since 3.0
* @access private
*/
private function _maybe_cloud( $service_tag ) {
if ( ! wp_http_validate_url( home_url() ) ) {
return false;
}
// we don't want the `img_optm-taken` to fail at any given time
if ( $service_tag == self::IMGOPTM_TAKEN ) {
return true;
}
$expiration_req = self::EXPIRATION_REQ;
// Limit frequent unfinished request to 5min
$timestamp_tag = 'curr_request.';
if ( $service_tag == self::SVC_IMG_OPTM . '-' . Img_Optm::TYPE_NEW_REQ ) {
$timestamp_tag = 'last_request.';
if ( $this->has_pkg( self::SVC_IMG_OPTM, self::BM_IMG_OPTM_PRIO ) ) {
$expiration_req /= 10;
}
}
else {
// For all other requests, if is under debug mode, will always allow
if ( $this->conf( self::O_DEBUG ) && $this->_api_key ) {
return true;
}
}
if ( ! empty( $this->_summary[ $timestamp_tag . $service_tag ] ) ) {
$expired = $this->_summary[ $timestamp_tag . $service_tag ] + $expiration_req - time();
if ( $expired > 0 ) {
self::debug( "❌ try [$service_tag] after $expired seconds" );
if ( $service_tag !== self::API_VER ) {
$msg = __( 'Cloud Error', 'litespeed-cache' ) . ': ' . sprintf( __( 'Please try after %1$s for service %2$s.', 'litespeed-cache' ), Utility::readable_time( $expired, 0, true ), '<code>' . $service_tag . '</code>' );
Admin_Display::error( array( 'cloud_trylater' => $msg ) );
}
return false;
}
}
if ( in_array( $service_tag, self::$_PUB_SVC_SET ) ) {
return true;
}
if ( ! $this->_api_key ) {
Admin_Display::error( Error::msg( 'lack_of_api_key' ) );
return false;
}
return true;
}
/**
* Post data to QUIC.cloud server
*
* @since 3.0
* @access public
*/
public static function post( $service, $data = false, $time_out = false ) {
$instance = self::cls();
return $instance->_post( $service, $data, $time_out );
}
/**
* Post data to cloud server
*
* @since 3.0
* @access private
*/
private function _post( $service, $data = false, $time_out = false ) {
$service_tag = $service;
if ( ! empty( $data[ 'action' ] ) ) {
$service_tag .= '-' . $data[ 'action' ];
}
if ( ! $this->_maybe_cloud( $service_tag ) ) {
return;
}
$server = $this->detect_cloud( $service );
if ( ! $server ) {
return;
}
$url = $server . '/' . $service;
self::debug( 'posting to : ' . $url );
$param = array(
'site_url' => home_url(),
'domain_key' => $this->_api_key,
'ver' => Core::VER,
'data' => $data,
);
$this->_summary[ 'curr_request.' . $service_tag ] = time();
self::save_summary();
$response = wp_remote_post( $url, array( 'body' => $param, 'timeout' => $time_out ?: 15, 'sslverify' => true ) );
return $this->_parse_response( $response, $service, $service_tag, $server );
}
/**
* Parse response JSON
* Mark the request successful if the response status is ok
*
* @since 3.0
*/
private function _parse_response( $response, $service, $service_tag, $server ) {
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
self::debug( 'failed to request: ' . $error_message );
if ( $service !== self::API_VER ) {
$msg = __( 'Failed to request via WordPress', 'litespeed-cache' ) . ': ' . $error_message . " [server] $server [service] $service";
Admin_Display::error( $msg );
// Force redetect node
self::debug( 'Node error, redetecting node [svc] ' . $service );
$this->detect_cloud( $service, true );
}
return;
}
$json = json_decode( $response[ 'body' ], true );
if ( ! is_array( $json ) ) {
self::debug( 'failed to decode response json: ' . $response[ 'body' ] );
if ( $service !== self::API_VER ) {
$msg = __( 'Failed to request via WordPress', 'litespeed-cache' ) . ': ' . $response[ 'body' ] . " [server] $server [service] $service";
Admin_Display::error( $msg );
// Force redetect node
self::debug( 'Node error, redetecting node [svc] ' . $service );
$this->detect_cloud( $service, true );
}
return;
}
if ( ! empty( $json[ '_code' ] ) ) {
if ( $json[ '_code' ] == 'heavy_load' || $json[ '_code' ] == 'redetect_node' ) {
// Force redetect node
self::debug( 'Node redetecting node [svc] ' . $service );
Admin_Display::info( __( 'Redetected node', 'litespeed-cache' ) . ': ' . Error::msg( $json[ '_code' ] ) );
$this->detect_cloud( $service, true );
}
}
if ( ! empty( $json[ '_503' ] ) ) {
self::debug( 'service 503 unavailable temporarily. ' . $json[ '_503' ] );
$msg = __( 'We are working hard to improve your online service experience. The service will be unavailable while we work. We apologize for any inconvenience.', 'litespeed-cache' );
$msg .= ' ' . $json[ '_503' ] . " [server] $server [service] $service";
Admin_Display::error( $msg );
// Force redetect node
self::debug( 'Node error, redetecting node [svc] ' . $service );
$this->detect_cloud( $service, true );
return;
}
if ( ! empty( $json[ '_info' ] ) ) {
self::debug( '_info: ' . $json[ '_info' ] );
$msg = __( 'Message from QUIC.cloud server', 'litespeed-cache' ) . ': ' . $json[ '_info' ];
$msg .= $this->_parse_link( $json );
Admin_Display::info( $msg );
unset( $json[ '_info' ] );
}
if ( ! empty( $json[ '_note' ] ) ) {
self::debug( '_note: ' . $json[ '_note' ] );
$msg = __( 'Message from QUIC.cloud server', 'litespeed-cache' ) . ': ' . $json[ '_note' ];
$msg .= $this->_parse_link( $json );
Admin_Display::note( $msg );
unset( $json[ '_note' ] );
}
if ( ! empty( $json[ '_success' ] ) ) {
self::debug( '_success: ' . $json[ '_success' ] );
$msg = __( 'Good news from QUIC.cloud server', 'litespeed-cache' ) . ': ' . $json[ '_success' ];
$msg .= $this->_parse_link( $json );
Admin_Display::succeed( $msg );
unset( $json[ '_success' ] );
}
// Upgrade is required
if ( ! empty( $json[ '_err_req_v' ] ) ) {
self::debug( '_err_req_v: ' . $json[ '_err_req_v' ] );
$msg = sprintf( __( '%1$s plugin version %2$s required for this action.', 'litespeed-cache' ), Core::NAME, 'v' . $json[ '_err_req_v' ] . '+' ) . " [server] $server [service] $service";
// Append upgrade link
$msg2 = ' ' . GUI::plugin_upgrade_link( Core::NAME, Core::PLUGIN_NAME, $json[ '_err_req_v' ] );
$msg2 .= $this->_parse_link( $json );
Admin_Display::error( $msg . $msg2 );
return;
}
// Parse _carry_on info
if ( ! empty( $json[ '_carry_on' ] ) ) {
self::debug( 'Carry_on usage', $json[ '_carry_on' ] );
// Store generic info
foreach ( array( 'usage', 'promo' ) as $v ) {
if ( ! empty( $json[ '_carry_on' ][ $v ] ) ) {
switch ( $v ) {
case 'usage':
$usage_svc_tag = in_array( $service, array( self::SVC_CCSS, self::SVC_UCSS ) ) ? self::SVC_PAGE_OPTM : $service;
$this->_summary[ 'usage.' . $usage_svc_tag ] = $json[ '_carry_on' ][ $v ];
break;
case 'promo':
if ( empty( $this->_summary[ $v ] ) || ! is_array( $this->_summary[ $v ] ) ) {
$this->_summary[ $v ] = array();
}
$this->_summary[ $v ][] = $json[ '_carry_on' ][ $v ];
break;
default:
break;
}
}
}
self::save_summary();
unset( $json[ '_carry_on' ] );
}
// Parse general error msg
if ( empty( $json[ '_res' ] ) || $json[ '_res' ] !== 'ok' ) {
$json_msg = ! empty( $json[ '_msg' ] ) ? $json[ '_msg' ] : 'unknown';
self::debug( '❌ _err: ' . $json_msg );
$msg = __( 'Failed to communicate with QUIC.cloud server', 'litespeed-cache' ) . ': ' . Error::msg( $json_msg ) . " [server] $server [service] $service";
$msg .= $this->_parse_link( $json );
Admin_Display::error( $msg );
// Site not on QC, delete invalid domain key
if ( $json_msg == 'site_not_registered' || $json_msg == 'err_key' ) {
$this->cls( 'Conf' )->update_confs( array( self::O_API_KEY => '' ) );
$msg = __( 'Site not recognized. Domain Key has been automatically removed. Please request a new one.', 'litespeed-cache' );
$msg .= Doc::learn_more( admin_url( 'admin.php?page=litespeed-general' ), __( 'Click here to set.', 'litespeed-cache' ), true, false, true );
$msg .= Doc::learn_more( 'https://docs.litespeedtech.com/lscache/lscwp/general/#domain-key', false, false, false, true );
Admin_Display::error( $msg, false, true );
}
return;
}
unset( $json[ '_res' ] );
if ( ! empty( $json[ '_msg' ] ) ) {
unset( $json[ '_msg' ] );
}
$this->_summary[ 'last_request.' . $service_tag ] = $this->_summary[ 'curr_request.' . $service_tag ];
$this->_summary[ 'curr_request.' . $service_tag ] = 0;
self::save_summary();
if ( $json ) {
self::debug2( 'response ok', $json );
}
else {
self::debug2( 'response ok' );
}
// Only successful request return Array
return $json;
}
/**
* Show promo from cloud
*
* @since 3.0
* @access public
*/
public function show_promo() {
// if ( ! $this->_api_key && ! defined( 'LITESPEED_DISMISS_DOMAIN_KEY' ) ) {
// Admin_Display::error( Error::msg( 'lack_of_api_key' ), true );
// }
if ( empty( $this->_summary[ 'promo' ] ) ) {
return;
}
require_once LSCWP_DIR . 'tpl/banner/cloud_promo.tpl.php' ;
}
/**
* Clear promo from cloud
*
* @since 3.0
* @access private
*/
private function _clear_promo() {
if ( count( $this->_summary[ 'promo' ] ) > 1 ) {
array_shift( $this->_summary[ 'promo' ] );
}
else {
$this->_summary[ 'promo' ] = array();
}
self::save_summary();
}
/**
* Parse _links from json
*
* @since 1.6.5
* @since 1.6.7 Self clean the parameter
* @access private
*/
private function _parse_link( &$json ) {
$msg = '';
if ( ! empty( $json[ '_links' ] ) ) {
foreach ( $json[ '_links' ] as $v ) {
$msg .= ' ' . sprintf( '<a href="%s" class="%s" target="_blank">%s</a>', $v[ 'link' ], ! empty( $v[ 'cls' ] ) ? $v[ 'cls' ] : '', $v[ 'title' ] );
}
unset( $json[ '_links' ] );
}
return $msg;
}
/**
* Request callback validation from Cloud
*
* @since 3.0
* @access public
*/
public function ip_validate() {
if ( empty( $_POST[ 'hash' ] ) ) {
self::debug( 'Lack of hash param' );
return self::err( 'lack_of_param' );
}
if ( empty( $this->_api_key ) ) {
self::debug( 'Lack of API key' );
return self::err( 'lack_of_api_key' );
}
$to_validate = substr( $this->_api_key, 0, 4 );
if ( $_POST[ 'hash' ] !== md5( $to_validate ) ) {
self::debug( '__callback IP request hash wrong: md5(' . $to_validate . ') !== ' . $_POST[ 'hash' ] );
return self::err( 'err_hash' );
}
Control::set_nocache( 'Cloud IP hash validation' );
$res_hash = substr( $this->_api_key, 2, 4 );
self::debug( '__callback IP request hash: md5(' . $res_hash . ')' );
return self::ok( array( 'hash' => md5( $res_hash ) ) );
}
/**
* Can apply for a new token or not
*
* @since 3.0
*/
public function can_token() {
return empty( $this->_summary[ 'token_ts' ] ) || time() - $this->_summary[ 'token_ts' ] > self::EXPIRATION_TOKEN;
}
/**
* Send request for domain key, get json [ 'token' => 'asdfasdf' ]
*
* @since 3.0
* @access public
*/
public function gen_key() {
$data = array(
'site_url' => home_url(),
'rest' => function_exists( 'rest_get_url_prefix' ) ? rest_get_url_prefix() : apply_filters( 'rest_url_prefix', 'wp-json' ),
'server_ip' => $this->conf( self::O_SERVER_IP ),
);
if ( ! empty( $this->_summary[ 'token' ] ) ) {
$data[ 'token' ] = $this->_summary[ 'token' ];
}
$response = wp_remote_get( self::CLOUD_SERVER . '/d/req_key?data=' . Utility::arr2str( $data ) );
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
self::debug( 'failed to gen_key: ' . $error_message );
Admin_Display::error( __( 'Cloud Error', 'litespeed-cache' ) . ': ' . $error_message );
return;
}
$json = json_decode( $response[ 'body' ], true );
// Save token option
if ( ! empty( $json[ 'token' ] ) ) {
$this->_summary[ 'token' ] = $json[ 'token' ];
$this->_summary[ 'token_ts' ] = time();
if ( ! empty( $this->_summary[ 'apikey_ts' ] ) ) {
unset( $this->_summary[ 'apikey_ts' ] );
}
self::save_summary();
}
// Parse general error msg
if ( empty( $json[ '_res' ] ) || $json[ '_res' ] !== 'ok' ) {
// clear current token
unset( $this->_summary[ 'token' ] );
self::save_summary();
$json_msg = ! empty( $json[ '_msg' ] ) ? $json[ '_msg' ] : 'unknown';
self::debug( '❌ _err: ' . $json_msg );
$msg = __( 'Failed to communicate with QUIC.cloud server', 'litespeed-cache' ) . ': ' . Error::msg( $json_msg );
$msg .= $this->_parse_link( $json );
Admin_Display::error( $msg );
return;
}
// This is a ok msg
if ( ! empty( $json[ '_msg' ] ) ) {
self::debug( '_msg: ' . $json[ '_msg' ] );
$msg = __( 'Message from QUIC.cloud server', 'litespeed-cache' ) . ': ' . Error::msg( $json[ '_msg' ] );
$msg .= $this->_parse_link( $json );
Admin_Display::info( $msg );
return;
}
self::debug( '✅ send request for key successfully.' );
Admin_Display::succeed( __( 'Applied for Domain Key successfully. Please wait for result. Domain Key will be automatically sent to your WordPress.', 'litespeed-cache' ) );
}
/**
* Token callback validation from Cloud
*
* @since 3.0
* @access public
*/
public function token_validate() {
try {
$this->_validate_hash();
} catch( \Exception $e ) {
return self::err( $e->getMessage() );
}
Control::set_nocache( 'Cloud token validation' );
self::debug( '✅ __callback token validation passed' );
return self::ok( array( 'hash' => md5( substr( $this->_summary[ 'token' ], 3, 8 ) ) ) );
}
/**
* Callback for approval of api key after validated token and gen key from QUIC.cloud
*
* @since 3.0
* @access public
*/
public function save_apikey() {
// Validate token hash first
if ( empty( $_POST[ 'domain_key' ] ) || ! isset( $_POST[ 'is_linked' ] ) ) {
return self::err( 'lack_of_param' );
}
try {
$this->_validate_hash( 1 );
} catch( \Exception $e ) {
return self::err( $e->getMessage() );
}
// This doesn't need to sync QUIC conf but need to clear nodes
$this->cls( 'Conf' )->update_confs( array( self::O_API_KEY => $_POST[ 'domain_key' ] ) );
$this->_summary[ 'is_linked' ] = $_POST[ 'is_linked' ] ? 1 : 0;
$this->_summary[ 'apikey_ts' ] = time();
// Clear token
unset( $this->_summary[ 'token' ] );
self::save_summary();
self::debug( '✅ saved auth_key' );
Admin_Display::succeed( '🎊 ' . __( 'Congratulations, your Domain Key has been approved! The setting has been updated accordingly.', 'litespeed-cache' ) );
return self::ok();
}
/**
* Validate POST hash match local token or not
*
* @since 3.0
*/
private function _validate_hash( $offset = 0 ) {
if ( empty( $_POST[ 'hash' ] ) ) {
self::debug( 'Lack of hash param' );
throw new \Exception( 'lack_of_param' );
}
if ( empty( $this->_summary[ 'token' ] ) ) {
self::debug( 'token validate failed: token not exist' );
throw new \Exception( 'lack_of_local_token' );
}
if ( $_POST[ 'hash' ] !== md5( substr( $this->_summary[ 'token' ], $offset, 8 ) ) ) {
self::debug( 'token validate failed: token mismatch hash !== ' . $_POST[ 'hash' ] );
throw new \Exception( 'mismatch' );
}
}
/**
* If can link the domain to QC user or not
*
* @since 3.0
*/
public function can_link_qc() {
return empty( $this->_summary[ 'is_linked' ] ) && $this->_api_key;
}
/**
* Link the domain to QC user
*
* @since 3.0
*/
private function _link_to_qc() {
if ( ! $this->can_link_qc() ) {
return;
}
$data = array(
'site_url' => home_url(),
'domain_hash' => md5( substr( $this->_api_key, 0, 8 ) ),
'ref' => get_admin_url( null, 'admin.php?page=litespeed-general' ),
);
wp_redirect( self::CLOUD_SERVER_DASH . '/u/wp?data=' . Utility::arr2str( $data ) );
exit;
}
/**
* Update is_linked status if is a redirected back from QC
*
* @since 3.0
*/
public function update_is_linked_status() {
if ( empty( $_GET[ 'qc_res' ] ) || empty( $_GET[ 'domain_hash' ] ) ) {
return;
}
if ( ! $this->_api_key ) {
return;
}
if ( md5( substr( $this->_api_key, 2, 8 ) ) !== $_GET[ 'domain_hash' ] ) {
Admin_Display::error( __( 'Domain Key hash mismatch', 'litespeed-cache' ), true );
return;
}
$this->_summary[ 'is_linked' ] = 1;
self::save_summary();
// Drop QS
echo "<script>window.history.pushState( 'remove_gen_link', document.title, window.location.href.replace( '&qc_res=" . sanitize_key( $_GET[ 'qc_res' ] ) . "&domain_hash=" . sanitize_key( $_GET[ 'domain_hash' ] ) . "', '' ) );</script>";
}
/**
* Check if this visit is from cloud or not
*
* @since 3.0
*/
public function is_from_cloud() {
if ( empty( $this->_summary[ 'ips' ] ) || empty( $this->_summary[ 'ips_ts' ] ) || time() - $this->_summary[ 'ips_ts' ] > 86400 * self::TTL_IPS ) {
$this->_update_ips();
}
$res = $this->cls( 'Router' )->ip_access( $this->_summary[ 'ips' ] );
if ( ! $res ) {
self::debug( '❌ Not our cloud IP' );
// Refresh IP list for future detection
$this->_update_ips();
}
else {
self::debug( '✅ Passed Cloud IP verification' );
}
return $res;
}
/**
* Update Cloud IP list
*
* @since 4.2
*/
private function _update_ips() {
self::debug( 'Load remote Cloud IP list from ' . self::CLOUD_IPS );
$response = wp_remote_get( self::CLOUD_IPS . '?json' );
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
self::debug( 'failed to get ip whitelist: ' . $error_message );
throw new \Exception( 'Failed to fetch QUIC.cloud whitelist ' . $error_message );
}
$json = json_decode( $response[ 'body' ], true );
$this->_summary[ 'ips_ts' ] = time();
$this->_summary[ 'ips' ] = $json;
self::save_summary();
}
/**
* Return succeeded response
*
* @since 3.0
*/
public static function ok( $data = array() ) {
$data[ '_res' ] = 'ok';
return $data;
}
/**
* Return error
*
* @since 3.0
*/
public static function err( $code ) {
return array( '_res' => 'err', '_msg' => $code );
}
/**
* Handle all request actions from main cls
*
* @since 3.0
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ( $type ) {
case self::TYPE_CLEAR_CLOUD:
$this->clear_cloud();
break;
case self::TYPE_REDETECT_CLOUD:
if ( ! empty( $_GET[ 'svc' ] ) ) {
$this->detect_cloud( $_GET[ 'svc' ], true );
}
break;
case self::TYPE_CLEAR_PROMO:
$this->_clear_promo();
break;
case self::TYPE_GEN_KEY:
$this->gen_key();
break;
case self::TYPE_LINK:
$this->_link_to_qc();
break;
case self::TYPE_SYNC_USAGE:
$this->sync_usage();
$msg = __( 'Sync credit allowance with Cloud Server successfully.', 'litespeed-cache' ) ;
Admin_Display::succeed( $msg ) ;
break;
default:
break;
}
Admin::redirect();
}
}