| @@ -1,18 +1,22 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | -namespace ImageOptimizer\Modules\Oauth\Components; | |
| 3 | +namespace ImageOptimization\Modules\Oauth\Components; | |
| 4 | 4 | |
| 5 | -use ImageOptimizer\Classes\{ | |
| 6 | - Logger, | |
| 7 | - Utils | |
| 5 | +use ImageOptimization\Modules\Oauth\{ | |
| 6 | + Classes\Route_Base, | |
| 7 | + Components\Exceptions\Auth_Error, | |
| 8 | + Classes\Data, | |
| 8 | 9 | }; |
| 10 | +use ImageOptimization\Classes\Logger; | |
| 11 | +use ImageOptimization\Classes\Utils; | |
| 12 | +use ImageOptimization\Modules\Settings\Module as Settings_Module; | |
| 9 | 13 | |
| 10 | -use ImageOptimizer\Modules\Oauth\Classes\Data; | |
| 11 | -use ImageOptimizer\Modules\Settings\Module as Settings_Module; | |
| 14 | +use stdClass; | |
| 15 | +use Throwable; | |
| 12 | 16 | |
| 13 | 17 | if ( ! defined( 'ABSPATH' ) ) { |
| 14 | - exit; // Exit if accessed directly | |
| 18 | + exit; // Exit if accessed directly. | |
| 15 | 19 | } |
| 16 | 20 | |
| 17 | 21 | /** |
| 18 | 22 | * Class Connect |
| @@ -18,8 +22,9 @@ | ||
| 18 | 22 | * Class Connect |
| 19 | 23 | */ |
| 20 | 24 | class Connect { |
| 21 | 25 | const API_URL = 'https://my.elementor.com/api/connect/v1'; |
| 26 | + const STATUS_CHECK_TRANSIENT = 'image_optimizer_status_check'; | |
| 22 | 27 | |
| 23 | 28 | /** |
| 24 | 29 | * is_connected |
| 25 | 30 | * @return bool |
| @@ -40,9 +45,9 @@ | ||
| 40 | 45 | * maybe_handle_admin_connect_page |
| 41 | 46 | * @return bool |
| 42 | 47 | */ |
| 43 | 48 | public static function maybe_handle_admin_connect_page(): bool { |
| 44 | - if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'nonce_actionget_token' ) ) { | |
| 49 | + if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'get_token' ) ) { | |
| 45 | 50 | return false; |
| 46 | 51 | } |
| 47 | 52 | |
| 48 | 53 | $args = [ |
| @@ -74,10 +79,10 @@ | ||
| 74 | 79 | return; |
| 75 | 80 | } |
| 76 | 81 | |
| 77 | 82 | // validate nonce |
| 78 | - if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'nonce_actionget_token' ) ) { | |
| 79 | - wp_die( 'Nonce verification failed', 'image-optimizer' ); | |
| 83 | + if ( empty( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), 'get_token' ) ) { | |
| 84 | + wp_die( 'Nonce verification failed', 'image-optimization' ); | |
| 80 | 85 | } |
| 81 | 86 | |
| 82 | 87 | $token_response = wp_remote_request( self::API_URL . '/get_token', [ |
| 83 | 88 | 'method' => 'POST', |
| @@ -84,14 +89,14 @@ | ||
| 84 | 89 | 'body' => [ |
| 85 | 90 | 'app' => 'library', |
| 86 | 91 | 'grant_type' => 'authorization_code', |
| 87 | 92 | 'client_id' => Data::get_client_id(), |
| 88 | - 'code' => sanitize_text_field( $_GET['code'] ), | |
| 93 | + 'code' => isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : null, | |
| 89 | 94 | ], |
| 90 | 95 | ] ); |
| 91 | 96 | |
| 92 | 97 | if ( is_wp_error( $token_response ) ) { |
| 93 | - wp_die( $token_response->get_error_message(), 'image-optimizer' ); | |
| 98 | + wp_die( esc_html( $token_response->get_error_message() ), 'image-optimization' ); | |
| 94 | 99 | } |
| 95 | 100 | |
| 96 | 101 | $data = json_decode( wp_remote_retrieve_body( $token_response ), true ); |
| 97 | 102 | Data::set_connect_data( $data ); |
| @@ -108,45 +113,234 @@ | ||
| 108 | 113 | die(); |
| 109 | 114 | } |
| 110 | 115 | |
| 111 | 116 | /** |
| 112 | - * disconnect | |
| 117 | + * Gets required connection data from the service and generates a connect link. | |
| 118 | + * | |
| 119 | + * @return string User connect link | |
| 120 | + * | |
| 121 | + * @throws Auth_Error | |
| 113 | 122 | */ |
| 123 | + public static function initialize_connect(): string { | |
| 124 | + try { | |
| 125 | + $response = wp_remote_request( | |
| 126 | + self::API_URL . '/library/get_client_id', | |
| 127 | + [ | |
| 128 | + 'method' => 'POST', | |
| 129 | + 'body' => [ | |
| 130 | + 'local_id' => get_current_user_id(), | |
| 131 | + 'site_key' => Data::get_site_key(), | |
| 132 | + 'app' => 'library', | |
| 133 | + 'home_url' => trailingslashit( home_url() ), | |
| 134 | + 'source' => 'image-optimizer', | |
| 135 | + ], | |
| 136 | + ] | |
| 137 | + ); | |
| 138 | + } catch ( Throwable $t ) { | |
| 139 | + Logger::log( | |
| 140 | + Logger::LEVEL_ERROR, | |
| 141 | + 'Error while sending connection initialization request: ' . $t->getMessage() | |
| 142 | + ); | |
| 143 | + | |
| 144 | + throw new Auth_Error( esc_html( $t->getMessage() ) ); | |
| 145 | + } | |
| 146 | + | |
| 147 | + $data = json_decode( wp_remote_retrieve_body( $response ) ); | |
| 148 | + | |
| 149 | + if ( ! isset( $data->client_id ) || ! isset( $data->auth_secret ) ) { | |
| 150 | + Logger::log( | |
| 151 | + Logger::LEVEL_ERROR, | |
| 152 | + 'Invalid response from server: client id or auth secret are undefined' | |
| 153 | + ); | |
| 154 | + | |
| 155 | + throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); | |
| 156 | + } | |
| 157 | + | |
| 158 | + Data::set_client_data( $data->client_id, $data->auth_secret ); | |
| 159 | + | |
| 160 | + return add_query_arg( [ | |
| 161 | + 'utm_source' => 'image-optimizer-panel', | |
| 162 | + 'utm_campaign' => 'image-optimizer', | |
| 163 | + 'utm_medium' => 'wp-dash', | |
| 164 | + 'source' => 'generic', | |
| 165 | + 'action' => 'authorize', | |
| 166 | + 'response_type' => 'code', | |
| 167 | + 'client_id' => $data->client_id, | |
| 168 | + 'auth_secret' => $data->auth_secret, | |
| 169 | + 'state' => Data::get_connect_state( true ), | |
| 170 | + 'redirect_uri' => rawurlencode( add_query_arg( [ | |
| 171 | + 'page' => 'elementor-connect', | |
| 172 | + 'app' => 'library', | |
| 173 | + 'action' => 'get_token', | |
| 174 | + 'nonce' => wp_create_nonce( 'get_token' ), | |
| 175 | + ], admin_url( 'admin.php' ) ) ), | |
| 176 | + 'may_share_data' => 0, | |
| 177 | + 'reconnect_nonce' => wp_create_nonce( 'reconnect' ), | |
| 178 | + ], Route_Base::SITE_URL . 'library' ); | |
| 179 | + } | |
| 180 | + | |
| 181 | + /** | |
| 182 | + * Disconnects a user and removes connection data from the DB. | |
| 183 | + */ | |
| 114 | 184 | public static function disconnect() { |
| 115 | - $response = wp_remote_request( self::API_URL . '/disconnect', [ | |
| 116 | - 'method' => 'POST', | |
| 117 | - 'body' => [ | |
| 118 | - 'app' => 'library', | |
| 119 | - 'home_url' => trailingslashit( home_url() ), | |
| 120 | - 'client_id' => Data::get_client_id(), | |
| 121 | - 'access_token' => Data::get_access_token(), | |
| 122 | - ], | |
| 123 | - ] ); | |
| 185 | + do_action( Checkpoint::ON_DISCONNECT ); | |
| 124 | 186 | |
| 125 | - if ( is_wp_error( $response ) ) { | |
| 126 | - wp_die( $response->get_error_message(), 'image-optimizer' ); | |
| 187 | + try { | |
| 188 | + return wp_remote_request( self::API_URL . '/disconnect', [ | |
| 189 | + 'method' => 'POST', | |
| 190 | + 'body' => [ | |
| 191 | + 'app' => 'library', | |
| 192 | + 'home_url' => trailingslashit( home_url() ), | |
| 193 | + 'client_id' => Data::get_client_id(), | |
| 194 | + 'access_token' => Data::get_access_token(), | |
| 195 | + ], | |
| 196 | + ] ); | |
| 197 | + } catch ( Throwable $t ) { | |
| 198 | + Logger::log( Logger::LEVEL_ERROR, 'Error while sending disconnection request: ' . $t->getMessage() ); | |
| 199 | + | |
| 200 | + throw new Auth_Error( esc_html( $t->getMessage() ) ); | |
| 201 | + } finally { | |
| 202 | + Data::reset(); | |
| 127 | 203 | } |
| 204 | + } | |
| 128 | 205 | |
| 129 | - Data::reset(); | |
| 130 | - do_action( Checkpoint::ON_DISCONNECT ); | |
| 206 | + /** | |
| 207 | + * Sends an activation request and stores activation data in the DB. | |
| 208 | + * | |
| 209 | + * @param $license_key string License key to activate with. | |
| 210 | + * @return mixed | |
| 211 | + * | |
| 212 | + * @throws Auth_Error | |
| 213 | + */ | |
| 214 | + public static function activate( string $license_key ) { | |
| 215 | + try { | |
| 216 | + $response = Utils::get_api_client()->make_request( | |
| 217 | + 'POST', | |
| 218 | + 'activation/activate', | |
| 219 | + [], | |
| 220 | + [ 'key' => $license_key ] | |
| 221 | + ); | |
| 222 | + } catch ( Throwable $t ) { | |
| 223 | + Logger::log( Logger::LEVEL_ERROR, 'Error while sending activation request: ' . $t->getMessage() ); | |
| 224 | + | |
| 225 | + throw new Auth_Error( esc_html( $t->getMessage() ) ); | |
| 226 | + } | |
| 227 | + | |
| 228 | + if ( ! isset( $response->id ) ) { | |
| 229 | + Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); | |
| 230 | + | |
| 231 | + throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); | |
| 232 | + } | |
| 233 | + | |
| 234 | + Data::set_activation_state( $license_key ); | |
| 235 | + | |
| 236 | + do_action( Checkpoint::ON_ACTIVATE, $license_key ); | |
| 237 | + | |
| 238 | + return $response; | |
| 131 | 239 | } |
| 132 | 240 | |
| 133 | - public static function check_connect_status() { | |
| 241 | + /** | |
| 242 | + * Deactivate specific license and remove activation data from the DB. | |
| 243 | + * | |
| 244 | + * @param $license_key string License key to deactivate. | |
| 245 | + * | |
| 246 | + * @return mixed | |
| 247 | + * | |
| 248 | + * @throws Auth_Error | |
| 249 | + */ | |
| 250 | + public static function deactivate( string $license_key ) { | |
| 251 | + | |
| 252 | + do_action( Checkpoint::ON_DEACTIVATE, $license_key ); | |
| 253 | + | |
| 254 | + try { | |
| 255 | + $response = Utils::get_api_client()->make_request( | |
| 256 | + 'POST', | |
| 257 | + 'activation/deactivate', | |
| 258 | + [ | |
| 259 | + 'key' => $license_key, | |
| 260 | + ] | |
| 261 | + ); | |
| 262 | + } catch ( Throwable $t ) { | |
| 263 | + Logger::log( Logger::LEVEL_ERROR, 'Error while sending deactivation request: ' . $t->getMessage() ); | |
| 264 | + | |
| 265 | + throw new Auth_Error( esc_html( $t->getMessage() ) ); | |
| 266 | + } finally { | |
| 267 | + Data::delete_activation_state(); | |
| 268 | + } | |
| 269 | + | |
| 270 | + if ( ! isset( $response->id ) ) { | |
| 271 | + Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); | |
| 272 | + | |
| 273 | + throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); | |
| 274 | + } | |
| 275 | + | |
| 276 | + return $response; | |
| 277 | + } | |
| 278 | + | |
| 279 | + /** | |
| 280 | + * Fetches and returns a list of available licenses for a specific user. | |
| 281 | + * | |
| 282 | + * @return array Available subscriptions or an empty array | |
| 283 | + * | |
| 284 | + * @throws Auth_Error | |
| 285 | + */ | |
| 286 | + public static function get_subscriptions(): array { | |
| 287 | + try { | |
| 288 | + $response = Utils::get_api_client()->make_request( | |
| 289 | + 'POST', | |
| 290 | + 'activation/get-subscriptions' | |
| 291 | + ); | |
| 292 | + } catch ( Throwable $t ) { | |
| 293 | + Logger::log( Logger::LEVEL_ERROR, 'Error while fetching subscriptions: ' . $t->getMessage() ); | |
| 294 | + | |
| 295 | + throw new Auth_Error( esc_html( $t->getMessage() ) ); | |
| 296 | + } | |
| 297 | + | |
| 298 | + if ( ! isset( $response->subscriptions ) ) { | |
| 299 | + Logger::log( Logger::LEVEL_ERROR, 'Invalid response from server' ); | |
| 300 | + | |
| 301 | + throw new Auth_Error( esc_html__( 'Invalid response from server', 'image-optimization' ) ); | |
| 302 | + } | |
| 303 | + | |
| 304 | + return $response->subscriptions; | |
| 305 | + } | |
| 306 | + | |
| 307 | + public static function get_connect_status() { | |
| 134 | 308 | if ( ! self::is_connected() ) { |
| 309 | + Logger::log( Logger::LEVEL_INFO, 'Status getting error. Reason: User is not connected' ); | |
| 310 | + | |
| 311 | + return null; | |
| 312 | + } | |
| 313 | + | |
| 314 | + $cached_status = get_transient( self::STATUS_CHECK_TRANSIENT ); | |
| 315 | + | |
| 316 | + if ( $cached_status ) { | |
| 317 | + return $cached_status; | |
| 318 | + } | |
| 319 | + | |
| 320 | + $status = self::check_connect_status(); | |
| 321 | + | |
| 322 | + set_transient( self::STATUS_CHECK_TRANSIENT, $status, MINUTE_IN_SECONDS * 5 ); | |
| 323 | + | |
| 324 | + return $status; | |
| 325 | + } | |
| 326 | + | |
| 327 | + private static function check_connect_status() { | |
| 328 | + if ( ! self::is_connected() ) { | |
| 135 | 329 | Logger::log( Logger::LEVEL_INFO, 'Status check error. Reason: User is not connected' ); |
| 136 | 330 | |
| 137 | 331 | return null; |
| 138 | 332 | } |
| 139 | 333 | |
| 140 | - $response = Utils::get_api_client()->make_request( | |
| 141 | - 'POST', | |
| 142 | - 'status/check' | |
| 143 | - ); | |
| 144 | - | |
| 145 | - if ( is_wp_error( $response ) ) { | |
| 334 | + try { | |
| 335 | + $response = Utils::get_api_client()->make_request( | |
| 336 | + 'POST', | |
| 337 | + 'status/check' | |
| 338 | + ); | |
| 339 | + } catch ( Throwable $t ) { | |
| 146 | 340 | Logger::log( |
| 147 | 341 | Logger::LEVEL_ERROR, |
| 148 | - 'Status check error. Reason: ' . $response->get_error_message() | |
| 342 | + 'Status check error. Reason: ' . $t->getMessage() | |
| 149 | 343 | ); |
| 150 | 344 | |
| 151 | 345 | return null; |
| 152 | 346 | } |
| @@ -159,10 +353,23 @@ | ||
| 159 | 353 | |
| 160 | 354 | return $response; |
| 161 | 355 | } |
| 162 | 356 | |
| 163 | - public static function get_connect_route_url( $endpoint ): string { | |
| 164 | - return rest_url( 'image-optimizer/v1/connect/' . $endpoint ); | |
| 357 | + public static function update_usage_data( stdClass $new_usage_data ) { | |
| 358 | + $connect_status = self::get_connect_status(); | |
| 359 | + | |
| 360 | + if ( ! isset( $new_usage_data->allowed ) || ! isset( $new_usage_data->used ) ) { | |
| 361 | + return; | |
| 362 | + } | |
| 363 | + | |
| 364 | + if ( 0 === $new_usage_data->allowed - $new_usage_data->used ) { | |
| 365 | + $connect_status->status = 'expired'; | |
| 366 | + } | |
| 367 | + | |
| 368 | + $connect_status->quota = $new_usage_data->allowed; | |
| 369 | + $connect_status->used_quota = $new_usage_data->used; | |
| 370 | + | |
| 371 | + set_transient( self::STATUS_CHECK_TRANSIENT, $connect_status, MINUTE_IN_SECONDS * 5 ); | |
| 165 | 372 | } |
| 166 | 373 | |
| 167 | 374 | public function __construct() { |
| 168 | 375 | // handle connect if elementor is active |