This commit is contained in:
2024-05-20 15:37:46 +03:00
commit 00b7dbd0b7
10404 changed files with 3285853 additions and 0 deletions

View File

@ -0,0 +1,60 @@
<?php
/**
* Smush Abstract_API class that handles communications with WPMU DEV API: API class
*
* @since 3.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use Exception;
use WP_Error;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Abstract_API.
*/
abstract class Abstract_API {
/**
* API key.
*
* @since 3.0
*
* @var string
*/
public $api_key = '';
/**
* API request instance.
*
* @since 3.0
*
* @var Request
*/
protected $request;
/**
* API constructor.
*
* @since 3.0
*
* @param string $key API key.
*
* @throws Exception API Request exception.
*/
public function __construct( $key ) {
$this->api_key = $key;
// The Request class needs these to make requests.
if ( empty( $this->version ) || empty( $this->name ) ) {
throw new Exception( __( 'API instances require a version and name properties', 'wp-smushit' ), 404 );
}
$this->request = new Request( $this );
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace Smush\Core\Api;
class Backoff {
private $max_attempts = 5;
private $wait = 1;
private $use_jitter = true;
private $decider;
public function __construct() {
$this->set_decider( $this->get_default_decider() );
}
public function run( $callback ) {
$attempt = 0;
$try = true;
$result = null;
$max_attempts = $this->get_max_attempts();
while ( $try ) {
$this->wait( $attempt );
$result = call_user_func( $callback );
$attempt ++;
if ( $attempt >= $max_attempts ) {
$try = false;
} else {
$try = call_user_func( $this->get_decider(), $result );
}
}
return $result;
}
private function wait( $attempt ) {
if ( $attempt == 0 ) {
return;
}
usleep( $this->get_wait_time( $attempt ) * 1000 );
}
/**
* @return mixed
*/
private function get_max_attempts() {
return $this->max_attempts;
}
/**
* @param mixed $max_attempts
*
* @return Backoff
*/
public function set_max_attempts( $max_attempts ) {
$this->max_attempts = max( (int) $max_attempts, 0 );
return $this;
}
/**
* @return mixed
*/
private function get_wait_time( $attempt ) {
$wait_time = $attempt == 1
? $this->wait
: pow( 2, $attempt ) * $this->wait;
return $this->jitter( (int) $wait_time );
}
/**
* @return mixed
*/
private function get_initial_wait() {
return $this->wait;
}
/**
* @param mixed $wait
*
* @return Backoff
*/
public function set_wait( $wait ) {
$this->wait = $wait;
return $this;
}
/**
* @return mixed
*/
private function get_decider() {
return $this->decider;
}
/**
* @param mixed $decider
*
* @return Backoff
*/
public function set_decider( $decider ) {
$this->decider = $decider;
return $this;
}
private function get_default_decider() {
return function ( $result ) {
return is_wp_error( $result );
};
}
private function set_jitter( $useJitter ) {
$this->use_jitter = $useJitter;
}
public function enable_jitter() {
$this->set_jitter( true );
return $this;
}
public function disable_jitter() {
$this->set_jitter( false );
return $this;
}
private function jitter_enabled() {
return $this->use_jitter;
}
private function jitter( $wait_time ) {
if ( ! $this->jitter_enabled() ) {
return $wait_time;
}
$jitter_percentage = mt_rand( 1, 20 );
$add_or_subtract = array_rand( array(
- 1 => - 1,
+ 1 => + 1,
) );
$jitter = ( $wait_time * $jitter_percentage / 100 ) * $add_or_subtract;
return $wait_time + $jitter;
}
}

View File

@ -0,0 +1,164 @@
<?php
/**
* WPMU DEV Hub endpoints.
*
* Class allows syncing plugin data with the Hub.
*
* @since 3.7.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use Smush\Core\Array_Utils;
use Smush\Core\Settings;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Hub
*/
class Hub {
/**
* Endpoints array.
*
* @since 3.7.0
* @var array
*/
private $endpoints = array(
'get_stats',
'import_settings',
'export_settings',
);
/**
* @var Array_Utils
*/
private $array_utils;
/**
* Hub constructor.
*
* @since 3.7.0
*/
public function __construct() {
$this->array_utils = new Array_Utils();
add_filter( 'wdp_register_hub_action', array( $this, 'add_endpoints' ) );
}
/**
* Add Hub endpoints.
*
* Every Hub Endpoint name is build following the structure: 'smush-$endpoint-$action'
*
* @since 3.7.0
* @param array $actions Endpoint action.
* @return array
*/
public function add_endpoints( $actions ) {
foreach ( $this->endpoints as $endpoint ) {
$actions[ "smush_{$endpoint}" ] = array( $this, 'action_' . $endpoint );
}
return $actions;
}
/**
* Retrieve data for endpoint.
*
* @since 3.7.0
* @param array $params Parameters.
* @param string $action Action.
*/
public function action_get_stats( $params, $action ) {
$status = array();
$core = WP_Smush::get_instance()->core();
$settings = Settings::get_instance();
$status['cdn'] = $core->mod->cdn->is_active();
$status['lossy'] = $settings->get_lossy_level_setting();
$lazy = $settings->get_setting( 'wp-smush-lazy_load' );
$status['lazy'] = array(
'enabled' => $core->mod->lazy->is_active(),
'native' => is_array( $lazy ) && isset( $lazy['native'] ) ? $lazy['native'] : false,
);
$global_stats = $core->get_global_stats();
// Total, Smushed, Unsmushed, Savings.
$status['count_total'] = $this->array_utils->get_array_value( $global_stats, 'count_total' );
$status['count_smushed'] = $this->array_utils->get_array_value( $global_stats, 'count_smushed' );
// Considering the images to be resmushed.
$status['count_unsmushed'] = $this->array_utils->get_array_value( $global_stats, 'count_unsmushed' );
$status['savings'] = $this->get_savings_stats( $global_stats );
$status['dir'] = $this->array_utils->get_array_value( $global_stats, 'savings_dir_smush' );
wp_send_json_success( (object) $status );
}
private function get_savings_stats( $global_stats ) {
// TODO: Is better to update the new change on hub?
$map_stats_keys = array(
'size_before' => 'size_before',
'size_after' => 'size_after',
'percent' => 'savings_percent',
'human' => 'human_bytes',
'bytes' => 'savings_bytes',
'total_images' => 'count_images',
'resize_count' => 'count_resize',
'resize_savings' => 'savings_resize',
'conversion_savings' => 'savings_conversion',
);
$hub_savings_stats = array();
foreach ( $map_stats_keys as $hub_key => $global_stats_key ) {
$hub_savings_stats[ $hub_key ] = $this->array_utils->get_array_value( $global_stats, $global_stats_key );
}
return $hub_savings_stats;
}
/**
* Applies the given config sent by the Hub via the Dashboard plugin.
*
* @since 3.8.5
*
* @param object $config_data The config sent by the Hub.
*/
public function action_import_settings( $config_data ) {
if ( empty( $config_data->configs ) ) {
wp_send_json_error(
array(
'message' => __( 'Missing config data', 'wp-smushit' ),
)
);
}
// The Hub returns an object, we use an array.
$config_array = json_decode( wp_json_encode( $config_data->configs ), true );
$configs_handler = new \Smush\Core\Configs();
$configs_handler->apply_config( $config_array );
wp_send_json_success();
}
/**
* Exports the current settings as a config for the Hub.
*
* @since 3.8.5
*/
public function action_export_settings() {
$configs_handler = new \Smush\Core\Configs();
$config = $configs_handler->get_config_from_current();
wp_send_json_success( $config['config'] );
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Smush\Core\Api;
use WP_Error;
class Request_Multiple {
public function do_requests( $requests, $options ) {
$on_complete = ! empty( $options['complete'] )
? $options['complete']
: '__return_false';
self::request_multiple( $requests, array_merge(
$options,
array(
'complete' => function ( $response, $key ) use ( &$requests, $on_complete ) {
// Convert to a response that looks like standard WP HTTP API responses
$response = $this->multi_to_singular_response( $response );
// Call the actual on complete callback
call_user_func( $on_complete, $response, $key );
},
)
) );
}
private function multi_to_singular_response( $multi_response ) {
if ( is_a( $multi_response, self::get_requests_exception_class_name() ) ) {
return new WP_Error(
$multi_response->getType(),
$multi_response->getMessage()
);
} else {
return array(
'body' => $multi_response->body,
'response' => array( 'code' => $multi_response->status_code ),
);
}
}
/** \Requests lib are deprecated on WP 6.2.0 */
private static function get_wp_requests_class_name() {
return class_exists('\WpOrg\Requests\Requests') ? '\WpOrg\Requests\Requests' : '\Requests';
}
private static function request_multiple( $requests, $options = array() ) {
$wp_requests_class_name = self::get_wp_requests_class_name();
return $wp_requests_class_name::request_multiple( $requests, $options );
}
private static function get_requests_exception_class_name() {
return class_exists('\WpOrg\Requests\Exception') ? '\WpOrg\Requests\Exception' : '\Requests_Exception';
}
}

View File

@ -0,0 +1,343 @@
<?php
/**
* API request class: Request
*
* Handles all the internal stuff to form and process a proper API request.
*
* @since 3.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use Exception;
use WP_Error;
use Smush\Core\Helper;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Request.
*/
class Request {
/**
* API service.
*
* @since 3.0
*
* @var null|API
*/
private $service = null;
/**
* Request max timeout.
*
* @since 3.0
*
* @var int
*/
private $timeout = 15;
/**
* Header arguments.
*
* @since 3.0
*
* @var array
*/
private $headers = array();
/**
* POST arguments.
*
* @since 3.0
*
* @var array
*/
private $post_args = array();
/**
* GET arguments.
*
* @since 3.0
*
* @var array
*/
private $get_args = array();
/**
* Request constructor.
*
* @since 3.0
*
* @param Abstract_API $service API service.
*
* @throws Exception Init exception.
*/
public function __construct( $service ) {
if ( ! $service instanceof Abstract_API ) {
throw new Exception( __( 'Invalid API service.', 'wp-smushit' ), 404 );
}
$this->service = $service;
}
/**
* Get the current site URL.
*
* The network_site_url() of the WP installation. (Or network_home_url if not passing an API key).
*
* @since 3.0
*
* @return string
*/
public function get_this_site() {
if ( defined( 'WP_SMUSH_API_DOMAIN' ) && WP_SMUSH_API_DOMAIN ) {
return WP_SMUSH_API_DOMAIN;
}
return network_site_url();
}
/**
* Set request timeout.
*
* @since 3.0
*
* @param int $timeout Request timeout (seconds).
*/
public function set_timeout( $timeout ) {
$this->timeout = $timeout;
}
/**
* Add a new request argument for POST requests.
*
* @since 3.0
*
* @param string $name Argument name.
* @param string $value Argument value.
*/
public function add_post_argument( $name, $value ) {
$this->post_args[ $name ] = $value;
}
/**
* Add a new request argument for GET requests.
*
* @since 3.0
*
* @param string $name Argument name.
* @param string $value Argument value.
*/
public function add_get_argument( $name, $value ) {
$this->get_args[ $name ] = $value;
}
/**
* Add a new request argument for GET requests.
*
* @since 3.0
*
* @param string $name Argument name.
* @param string $value Argument value.
*/
public function add_header_argument( $name, $value ) {
$this->headers[ $name ] = $value;
}
/**
* Make a POST API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function post( $path, $data = array() ) {
try {
$result = $this->request( $path, $data );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a GET API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function get( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'get' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a HEAD API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function head( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'head' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a PATCH API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function patch( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'patch' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Make a DELETE API call.
*
* @since 3.0
*
* @param string $path Endpoint route.
* @param array $data Data array.
*
* @return mixed|WP_Error
*/
public function delete( $path, $data = array() ) {
try {
$result = $this->request( $path, $data, 'delete' );
return $result;
} catch ( Exception $e ) {
return new WP_Error( $e->getCode(), $e->getMessage() );
}
}
/**
* Get API endpoint URL for request.
*
* @since 3.0
*
* @param string $path Endpoint path.
*
* @return string
*/
private function get_api_url( $path = '' ) {
$base = defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && WPMUDEV_CUSTOM_API_SERVER
? WPMUDEV_CUSTOM_API_SERVER
: 'https://wpmudev.com/';
$url = "$base/api/{$this->service->name}/{$this->service->version}/";
$url = trailingslashit( $url . $path );
return $url;
}
/**
* Add authorization header.
*
* @since 3.0
*/
private function sign_request() {
if ( ! empty( $this->service->api_key ) ) {
$this->add_header_argument( 'Authorization', 'Basic ' . $this->service->api_key );
}
}
/**
* Make an API request.
*
* @since 3.0
*
* @param string $path API endpoint route.
* @param array $data Data array.
* @param string $method API method.
*
* @return array|WP_Error
*/
private function request( $path, $data = array(), $method = 'post' ) {
$url = $this->get_api_url( $path );
$this->sign_request();
$url = add_query_arg( $this->get_args, $url );
if ( 'post' !== $method && 'patch' !== $method && 'delete' !== $method ) {
$url = add_query_arg( $data, $url );
}
$args = array(
'user-agent' => WP_SMUSH_UA,
'headers' => $this->headers,
'sslverify' => false,
'method' => strtoupper( $method ),
'timeout' => $this->timeout,
);
if ( ! $args['timeout'] || 2 === $args['timeout'] ) {
$args['blocking'] = false;
}
switch ( strtolower( $method ) ) {
case 'patch':
case 'delete':
case 'post':
if ( is_array( $data ) ) {
$args['body'] = array_merge( $data, $this->post_args );
} else {
$args['body'] = $data;
}
$response = wp_remote_post( $url, $args );
break;
case 'head':
$response = wp_remote_head( $url, $args );
break;
case 'get':
$response = wp_remote_get( $url, $args );
break;
default:
$response = wp_remote_request( $url, $args );
break;
}
// Log error.
if ( is_wp_error( $response ) ) {
Helper::logger()->api()->error( sprintf( 'Error [%s->%s]: %s', $method, $path, $response->get_error_message() ) );
}
return $response;
}
}

View File

@ -0,0 +1,115 @@
<?php
/**
* Smush API class that handles communications with WPMU DEV API: API class
*
* @since 3.0
* @package Smush\Core\Api
*/
namespace Smush\Core\Api;
use WP_Error;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Smush_API.
*/
class Smush_API extends Abstract_API {
/**
* Endpoint name.
*
* @since 3.0
*
* @var string
*/
public $name = 'smush';
/**
* Endpoint version.
*
* @since 3.0
*
* @var string
*/
public $version = 'v1';
/**
* Check CDN status (same as verify the is_pro status).
*
* @since 3.0
*
* @param bool $manual If it's a manual check. Only manual on button click.
*
* @return mixed|WP_Error
*/
public function check( $manual = false ) {
return $this->backoff_sync( function () {
return $this->request->get(
"check/{$this->api_key}",
array(
'api_key' => $this->api_key,
'domain' => $this->request->get_this_site(),
)
);
}, $manual );
}
/**
* Enable CDN for site.
*
* @since 3.0
*
* @param bool $manual If it's a manual check. Overwrites the exponential back off.
*
* @return mixed|WP_Error
*/
public function enable( $manual = false ) {
return $this->backoff_sync( function () {
return $this->request->post(
'cdn',
array(
'api_key' => $this->api_key,
'domain' => $this->request->get_this_site(),
)
);
}, $manual );
}
private function backoff_sync( $operation, $manual ) {
$defaults = array(
'time' => time(),
'fails' => 0,
);
$last_run = (array) get_site_option( 'wp-smush-last_run_sync', $defaults );
if ( ! empty( $last_run['fails'] ) ) {
$backoff = min( pow( 5, $last_run['fails'] ), HOUR_IN_SECONDS ); // Exponential 5, 25, 125, 625, 3125, 3600 max.
if ( $last_run['fails'] && $last_run['time'] > ( time() - $backoff ) && ! $manual ) {
$last_run['time'] = time();
update_site_option( 'wp-smush-last_run_sync', $last_run );
return new WP_Error( 'api-backoff', __( '[WPMUDEV API] Skipped sync due to API error exponential backoff.', 'wp-smushit' ) );
}
}
$response = call_user_func( $operation );
$last_run['time'] = time();
// Clear the API backoff if it's a manual scan or the API call was a success.
if ( $manual || ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) ) {
$last_run['fails'] = 0;
} else {
// For network errors, perform exponential backoff.
$last_run['fails'] = $last_run['fails'] + 1;
}
update_site_option( 'wp-smush-last_run_sync', $last_run );
return $response;
}
}