PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.7.7
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.7.7
1.7.7 1.7.6 1.7.5 1.7.4 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 All 33 releases
← All changes | classes/client/client.php +128 -29 1.2.01.7.7 View file →
@@ -2,16 +2,19 @@
2 2
3 3 namespace ImageOptimization\Classes\Client;
4 4
5 5 use ImageOptimization\Classes\Exceptions\Client_Exception;
6 +use ImageOptimization\Classes\File_Utils;
6 7 use ImageOptimization\Classes\Image\Image;
7 -use ImageOptimization\Modules\Oauth\{
8 - Classes\Data,
9 - Components\Connect
10 -};
8 +use ImageOptimization\Classes\Logger;
9 +use ImageOptimization\Modules\Connect\Module as Connect_Module;
10 +use ImageOptimization\Modules\Optimization\Classes\Optimize_Image;
11 11 use ImageOptimization\Modules\Stats\Classes\Optimization_Stats;
12 +use Throwable;
12 13 use WP_Error;
13 14
15 +use ImageOptimization\Plugin;
16 +
14 17 if ( ! defined( 'ABSPATH' ) ) {
15 18 exit; // Exit if accessed directly.
16 19 }
17 20
@@ -19,10 +22,16 @@
19 22 * Class Client
20 23 */
21 24 class Client {
22 25 const BASE_URL = 'https://my.elementor.com/api/v2/image-optimizer/';
23 - const STATUS_CHECK = 'status/check';
26 + const BASE_URL_FEEDBACK = 'https://feedback-api.prod.apps.elementor.red/apps/api/v1/';
27 + const SITE_INFO = 'site/info';
28 + const SITE_INFO_TRANSIENT = 'image_optimizer_site_info';
29 + const SITE_INFO_TRANSIENT_ERROR = 'image_optimizer_site_info_error';
30 + const ERROR_CACHE_DURATION = MINUTE_IN_SECONDS * 5;
24 31
32 + private bool $refreshed = false;
33 +
25 34 public static ?Client $instance = null;
26 35
27 36 /**
28 37 * get_instance
@@ -31,13 +40,14 @@
31 40 public static function get_instance(): ?Client {
32 41 if ( ! self::$instance ) {
33 42 self::$instance = new self();
34 43 }
44 +
35 45 return self::$instance;
36 46 }
37 47
38 - public static function get_site_info(): array {
39 - return [
48 + public static function get_site_info( $endpoint = null ): array {
49 + $data = [
40 50 // Which API version is used.
41 51 'app_version' => IMAGE_OPTIMIZATION_VERSION,
42 52 // Which language to return.
43 53 'site_lang' => get_bloginfo( 'language' ),
@@ -44,13 +54,54 @@
44 54 // site to connect
45 55 'site_url' => trailingslashit( home_url() ),
46 56 // current user
47 57 'local_id' => get_current_user_id(),
58 +
59 + ];
60 +
61 + if ( Optimize_Image::IMAGE_OPTIMIZE_ENDPOINT === $endpoint ) {
48 62 // Media library stats
49 - 'media_data' => base64_encode( wp_json_encode( self::get_request_stats() ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
50 - ];
63 + $data['media_data'] = base64_encode( wp_json_encode( self::get_request_stats() ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
64 + }
65 +
66 + return $data;
51 67 }
52 68
69 + /**
70 + * Get site info
71 + * @return mixed|WP_Error|null
72 + */
73 + public static function get_subscription_info() {
74 + $cached_info = get_transient( self::SITE_INFO_TRANSIENT );
75 +
76 + if ( $cached_info ) {
77 + return $cached_info;
78 + }
79 +
80 + $cached_error = get_transient( self::SITE_INFO_TRANSIENT_ERROR );
81 +
82 + if ( $cached_error ) {
83 + Logger::debug( 'Status check skipped due to recent error: ' . $cached_error );
84 + return null;
85 + }
86 +
87 + try {
88 + $info = self::get_instance()->make_request( 'POST', self::SITE_INFO );
89 + } catch ( Throwable $t ) {
90 + $error_message = $t->getMessage();
91 +
92 + Logger::error( 'Cannot get site info response from service: ' . $error_message );
93 +
94 + set_transient( self::SITE_INFO_TRANSIENT_ERROR, $error_message, self::ERROR_CACHE_DURATION );
95 +
96 + return null;
97 + }
98 +
99 + set_transient( self::SITE_INFO_TRANSIENT, $info, DAY_IN_SECONDS );
100 +
101 + return $info;
102 + }
103 +
53 104 private static function get_request_stats(): array {
54 105 $optimization_stats = Optimization_Stats::get_image_stats();
55 106 $image_sizes = [];
56 107
@@ -77,9 +128,9 @@
77 128 $headers,
78 129 $this->is_connected() ? $this->generate_authentication_headers( $endpoint ) : []
79 130 );
80 131
81 - $body = array_replace_recursive( $body, $this->get_site_info() );
132 + $body = array_replace_recursive( $body, $this->get_site_info( $endpoint ) );
82 133
83 134 try {
84 135 if ( $file ) {
85 136 $boundary = wp_generate_password( 24, false );
@@ -94,9 +145,9 @@
94 145 $response = $this->request(
95 146 $method,
96 147 $endpoint,
97 148 [
98 - 'timeout' => 100,
149 + 'timeout' => 50,
99 150 'headers' => $headers,
100 151 'body' => $body,
101 152 ]
102 153 );
@@ -103,30 +154,50 @@
103 154
104 155 return ( new Client_Response( $response ) )->handle();
105 156 }
106 157
158 + public static function get_feedback_base_url() {
159 + return apply_filters( 'image_optimizer_feedback_base_url', self::BASE_URL_FEEDBACK );
160 + }
161 +
107 162 private static function get_remote_url( $endpoint ): string {
108 - return self::BASE_URL . $endpoint;
163 + $base_url = apply_filters( 'image_optimizer_client_get_base_url', self::BASE_URL );
164 +
165 + if ( strpos( $endpoint, 'feedback/' ) !== false ) {
166 + return self::get_feedback_base_url() . $endpoint;
167 + }
168 +
169 + return $base_url . $endpoint;
109 170 }
110 171
111 172 protected function is_connected(): bool {
112 - return Connect::is_connected();
173 + return Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_connected();
113 174 }
114 175
115 176 protected function generate_authentication_headers( $endpoint ): array {
116 - $headers = [
117 - 'data' => base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
118 - wp_json_encode( [ 'app' => 'library' ] )
119 - ),
120 - 'endpoint' => $endpoint,
121 - 'access_token' => Data::get_access_token(),
122 - 'client_id' => Data::get_client_id(),
123 - ];
177 + $connect_instance = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance;
124 178
125 - if ( Connect::is_activated() ) {
126 - $headers['key'] = Data::get_activation_state();
179 + if ( ! $connect_instance->get_is_connect_on_fly() ) {
180 + $headers = [
181 + 'data' => base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
182 + wp_json_encode( [ 'app' => 'library' ] )
183 + ),
184 + 'access_token' => $connect_instance->get_access_token() ?? '',
185 + 'client_id' => $connect_instance->get_client_id() ?? '',
186 + ];
187 +
188 + if ( $connect_instance->is_activated() ) {
189 + $headers['key'] = $connect_instance->get_activation_state() ?? '';
190 + }
191 + } else {
192 + $headers = $this->add_bearer_token([
193 + 'x-elementor-apps-connect' => true,
194 + ]);
127 195 }
128 196
197 + $headers['endpoint'] = $endpoint;
198 + $headers['x-elementor-apps'] = 'image-optimizer';
199 +
129 200 return $headers;
130 201 }
131 202
132 203 protected function request( $method, $endpoint, $args = [] ) {
@@ -163,9 +234,25 @@
163 234 if ( false === $body ) {
164 235 return new WP_Error( 422, 'Wrong Server Response' );
165 236 }
166 237
167 - if ( 200 !== $response_code ) {
238 + if ( Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_is_connect_on_fly() ) {
239 + // If the token is invalid, refresh it and try again once only.
240 + if ( ! $this->refreshed && ! empty( $body->message ) && ( false !== strpos( $body->message, 'Invalid Token' ) ) ) {
241 + Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->refresh_token();
242 + $this->refreshed = true;
243 + $args['headers'] = $this->add_bearer_token( $args['headers'] );
244 + return $this->request( $method, $endpoint, $args );
245 + }
246 + }
247 +
248 + // If there is mismatch then trigger the mismatch flow explicitly.
249 + if ( ! $this->refreshed && ! empty( $body->message ) && ( false !== strpos( $body->message, 'site url mismatch' ) ) ) {
250 + Connect_Module::get_connect()->data()->set_home_url( 'https://wrongurl' );
251 + return new WP_Error( 401, 'site url mismatch' );
252 + }
253 +
254 + if ( ! in_array( $response_code, [ 200, 201 ], true ) ) {
168 255 // In case $as_array = true.
169 256 $message = $body->message ?? wp_remote_retrieve_response_message( $response );
170 257 $message = is_array( $message ) ? join( ', ', $message ) : $message;
171 258 $code = isset( $body->code ) ? (int) $body->code : $response_code;
@@ -175,8 +262,15 @@
175 262
176 263 return $body;
177 264 }
178 265
266 + public function add_bearer_token( $headers ) {
267 + if ( $this->is_connected() ) {
268 + $headers['Authorization'] = 'Bearer ' . Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_access_token();
269 + }
270 + return $headers;
271 + }
272 +
179 273 /**
180 274 * get_upload_request_body
181 275 *
182 276 * @param array $body
@@ -185,11 +279,12 @@
185 279 * @param string $file_name
186 280 *
187 281 * @return string
188 282 * @throws Client_Exception
189 -*/
283 + */
190 284 private function get_upload_request_body( array $body, $file, string $boundary, string $file_name = '' ): string {
191 285 $payload = '';
286 +
192 287 // add all body fields as standard POST fields:
193 288 foreach ( $body as $name => $value ) {
194 289 $payload .= '--' . $boundary;
195 290 $payload .= "\r\n";
@@ -204,10 +299,13 @@
204 299 }
205 300 } else {
206 301 $image_mime = image_type_to_mime_type( exif_imagetype( $file ) );
207 302
208 - if ( ! in_array( $image_mime, Image::get_supported_mime_types(), true ) ) {
209 - throw new Client_Exception( "Unsupported mime type `$image_mime`" );
303 + if (
304 + ! in_array( $image_mime, Image::get_supported_mime_types(), true ) &&
305 + ( 'application/octet-stream' === $image_mime && 'avif' !== File_Utils::get_extension( $file ) )
306 + ) {
307 + throw new Client_Exception( esc_html( "Unsupported mime type `$image_mime`" ) );
210 308 }
211 309
212 310 if ( empty( $file_name ) ) {
213 311 $file_name = basename( $file );
@@ -230,15 +328,16 @@
230 328 * @return string
231 329 */
232 330 private function get_file_payload( string $filename, string $file_type, string $file_path, string $boundary ): string {
233 331 $name = $filename ?? basename( $file_path );
234 - $mine_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type;
332 + $mime_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type;
235 333 $payload = '';
334 +
236 335 // Upload the file
237 336 $payload .= '--' . $boundary;
238 337 $payload .= "\r\n";
239 338 $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"; filename="' . esc_attr( $name ) . '"' . "\r\n";
240 - $payload .= 'Content-Type: ' . $mine_type . "\r\n";
339 + $payload .= 'Content-Type: ' . $mime_type . "\r\n";
241 340 $payload .= "\r\n";
242 341 $payload .= file_get_contents( $file_path );
243 342 $payload .= "\r\n";
244 343