namespace ImageOptimization\Modules\Oauth\Components;
use ImageOptimization\Modules\Oauth\{
Components\Exceptions\Auth_Error,
use ImageOptimization\Classes\Logger;
use ImageOptimization\Classes\Utils;
use ImageOptimization\Modules\Settings\Module as Settings_Module;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
const API_URL = 'https://my.elementor.com/api/connect/v1';
const STATUS_CHECK_TRANSIENT = 'image_optimizer_status_check';
public static function is_connected(): bool {
return ! empty( Data::get_connect_data()['access_token'] );
public static function is_activated(): bool {
return ! empty( Data::get_activation_state() );
* maybe_handle_admin_connect_page
public static function maybe_handle_admin_connect_page(): bool {
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'nonce_actionget_token' ) ) {
'page' => 'elementor-connect',
'state' => Data::get_connect_state(),
foreach ( $args as $key => $value ) {
if ( ! isset( $_GET[ $key ] ) || $_GET[ $key ] !== $value ) {
if ( ! isset( $_GET['nonce'] ) || ! isset( $_GET['code'] ) ) {
* handle_elementor_connect_admin
public function handle_elementor_connect_admin(): void {
if ( ! self::maybe_handle_admin_connect_page() ) {
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'nonce_actionget_token' ) ) {
wp_die( 'Nonce verification failed', 'image-optimization' );
$token_response = wp_remote_request( self::API_URL . '/get_token', [
'grant_type' => 'authorization_code',
'client_id' => Data::get_client_id(),
'code' => sanitize_text_field( $_GET['code'] ),
if ( is_wp_error( $token_response ) ) {
wp_die( $token_response->get_error_message(), 'image-optimization' );
$data = json_decode( wp_remote_retrieve_body( $token_response ), true );
Data::set_connect_data( $data );
do_action( Checkpoint::ON_CONNECT );
Data::delete_connect_state();
wp_redirect( add_query_arg( [
'page' => Settings_Module::SETTING_BASE_SLUG,
], admin_url( 'admin.php' ) ) );
* Gets required connection data from the service and generates a connect link.
* @return string User connect link
public static function initialize_connect(): string {
$response = wp_remote_request(
self::API_URL . '/library/get_client_id',
'local_id' => get_current_user_id(),
'site_key' => Data::get_site_key(),
'home_url' => trailingslashit( home_url() ),
'source' => 'image-optimizer',
} catch ( Throwable $t ) {
'Error while sending connection initialization request: ' . $t->getMessage()
throw new Auth_Error( $t->getMessage() );
$data = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! isset( $data->client_id ) || ! isset( $data->auth_secret ) ) {
'Invalid response from server: client id or auth secret are undefined'
throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) );
Data::set_client_data( $data->client_id, $data->auth_secret );
'utm_source' => 'image-optimizer-panel',
'utm_campaign' => 'image-optimizer',
'utm_medium' => 'wp-dash',
'response_type' => 'code',
'client_id' => $data->client_id,
'auth_secret' => $data->auth_secret,
'state' => Data::get_connect_state( true ),
'redirect_uri' => rawurlencode( add_query_arg( [
'page' => 'elementor-connect',
'nonce' => wp_create_nonce( 'nonce_action' . 'get_token' ),
], admin_url( 'admin.php' ) ) ),
'reconnect_nonce' => wp_create_nonce( 'nonce_action' . 'reconnect' ),
], Route_Base::SITE_URL . 'library' );
* Disconnects a user and removes connection data from the DB.
public static function disconnect() {
do_action( Checkpoint::ON_DISCONNECT );
return wp_remote_request( self::API_URL . '/disconnect', [
'home_url' => trailingslashit( home_url() ),
'client_id' => Data::get_client_id(),
'access_token' => Data::get_access_token(),
} catch ( Throwable $t ) {
Logger::log( Logger::LEVEL_ERROR, 'Error while sending disconnection request: ' . $t->getMessage() );
throw new Auth_Error( $t->getMessage() );
* Sends an activation request and stores activation data in the DB.
* @param $license_key string License key to activate with.
public static function activate( string $license_key ) {
$response = Utils::get_api_client()->make_request(
[ 'key' => $license_key ]
} catch ( Throwable $t ) {
Logger::log( Logger::LEVEL_ERROR, 'Error while sending activation request: ' . $t->getMessage() );
throw new Auth_Error( $t->getMessage() );
if ( ! isset( $response->id ) ) {
Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' );
throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) );
Data::set_activation_state( $license_key );
do_action( Checkpoint::ON_ACTIVATE, $license_key );
* Deactivate specific license and remove activation data from the DB.
* @param $license_key string License key to deactivate.
public static function deactivate( string $license_key ) {
do_action( Checkpoint::ON_DEACTIVATE, $license_key );
$response = Utils::get_api_client()->make_request(
} catch ( Throwable $t ) {
Logger::log( Logger::LEVEL_ERROR, 'Error while sending deactivation request: ' . $t->getMessage() );
throw new Auth_Error( $t->getMessage() );
Data::delete_activation_state();
if ( ! isset( $response->id ) ) {
Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' );
throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) );
* Fetches and returns a list of available licenses for a specific user.
* @return array Available subscriptions or an empty array
public static function get_subscriptions(): array {
$response = Utils::get_api_client()->make_request(
'activation/get-subscriptions'
} catch ( Throwable $t ) {
Logger::log( Logger::LEVEL_ERROR, 'Error while fetching subscriptions: ' . $t->getMessage() );
throw new Auth_Error( $t->getMessage() );
if ( ! isset( $response->subscriptions ) ) {
Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' );
throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) );
return $response->subscriptions;
public static function get_connect_status() {
if ( ! self::is_connected() ) {
Logger::log( Logger::LEVEL_INFO, 'Status getting error. Reason: User is not connected' );
$cached_status = get_transient( self::STATUS_CHECK_TRANSIENT );
$status = self::check_connect_status();
set_transient( self::STATUS_CHECK_TRANSIENT, $status, MINUTE_IN_SECONDS * 5 );
private static function check_connect_status() {
if ( ! self::is_connected() ) {
Logger::log( Logger::LEVEL_INFO, 'Status check error. Reason: User is not connected' );
$response = Utils::get_api_client()->make_request(
} catch ( Throwable $t ) {
'Status check error. Reason: ' . $t->getMessage()
if ( ! isset( $response->status ) ) {
Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' );
public static function update_usage_data( stdClass $new_usage_data ) {
$connect_status = self::get_connect_status();
if ( ! isset( $new_usage_data->allowed ) || ! isset( $new_usage_data->used ) ) {
if ( 0 === $new_usage_data->allowed - $new_usage_data->used ) {
$connect_status->status = 'expired';
$connect_status->quota = $new_usage_data->allowed;
$connect_status->used_quota = $new_usage_data->used;
set_transient( self::STATUS_CHECK_TRANSIENT, $connect_status, MINUTE_IN_SECONDS * 5 );
public function __construct() {
// handle connect if elementor is active
add_action( 'load-elementor_page_elementor-connect', [ $this, 'handle_elementor_connect_admin' ], 9 );
// handle connect if elementor is not active
add_action( '_admin_menu', [ $this, 'handle_elementor_connect_admin' ] );