PluginProbe
Image Optimization – Compress Images and Convert to WebP or AVIF / 1.6.6
Image Optimization – Compress Images and Convert to WebP or AVIF v1.6.6
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 1.6.6 All 32 releases
image-optimization / classes / client / client.php

client.php in Image Optimization – Compress Images and Convert to WebP or AVIF 1.6.6, at classes/client/client.php

312 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImageOptimization\Classes\Client;
4
5 use ImageOptimization\Classes\Exceptions\Client_Exception;
6 use ImageOptimization\Classes\File_Utils;
7 use ImageOptimization\Classes\Image\Image;
8 use ImageOptimization\Classes\Logger;
9 use ImageOptimization\Modules\Stats\Classes\Optimization_Stats;
10 use Throwable;
11 use WP_Error;
12
13 use ImageOptimization\Plugin;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Class Client
21 */
22 class Client {
23 const BASE_URL = 'https://my.elementor.com/api/v2/image-optimizer/';
24 const STATUS_CHECK = 'status/check';
25 const SITE_INFO = 'site/info';
26 const SITE_INFO_TRANSIENT = 'image_optimizer_site_info_transient';
27
28
29 private bool $refreshed = false;
30
31 public static ?Client $instance = null;
32
33 /**
34 * get_instance
35 * @return Client|null
36 */
37 public static function get_instance(): ?Client {
38 if ( ! self::$instance ) {
39 self::$instance = new self();
40 }
41 return self::$instance;
42 }
43
44 public static function get_site_info( $endpoint = null ): array {
45 $data = [
46 // Which API version is used.
47 'app_version' => IMAGE_OPTIMIZATION_VERSION,
48 // Which language to return.
49 'site_lang' => get_bloginfo( 'language' ),
50 // site to connect
51 'site_url' => trailingslashit( home_url() ),
52 // current user
53 'local_id' => get_current_user_id(),
54
55 ];
56
57 if ( $endpoint !== self::STATUS_CHECK ) {
58 // Media library stats
59 $data['media_data'] = base64_encode( wp_json_encode( self::get_request_stats() ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
60 }
61
62 return $data;
63 }
64
65 /**
66 * Get site info
67 * @return mixed|WP_Error|null
68 */
69 public static function get_subscription_info() {
70 $info = get_transient( self::SITE_INFO_TRANSIENT );
71 if ( ! $info ) {
72 try {
73 $info = self::get_instance()->make_request( 'POST', self::SITE_INFO );
74 } catch ( Throwable $t ) {
75 Logger::log( Logger::LEVEL_ERROR, 'Cannot get site info response from service: ' . $t->getMessage() );
76 return null;
77 }
78
79 set_transient( self::SITE_INFO_TRANSIENT, $info, ( 24 * 60 * 60 ) );
80 }
81 return $info;
82 }
83
84 private static function get_request_stats(): array {
85 $optimization_stats = Optimization_Stats::get_image_stats();
86 $image_sizes = [];
87
88 foreach ( wp_get_registered_image_subsizes() as $image_size_key => $image_size_data ) {
89 $image_sizes[] = [
90 'label' => $image_size_key,
91 'size' => "{$image_size_data['width']}x{$image_size_data['height']}",
92 ];
93 }
94
95 return [
96 'not_optimized_images' => $optimization_stats['total_image_count'] - $optimization_stats['optimized_image_count'],
97 'optimized_images' => $optimization_stats['optimized_image_count'],
98 'images_sizes' => $image_sizes,
99 ];
100 }
101
102 public function make_request( $method, $endpoint, $body = [], array $headers = [], $file = false, $file_name = '' ) {
103 $headers = array_replace_recursive([
104 'x-elementor-image-optimizer' => IMAGE_OPTIMIZATION_VERSION,
105 ], $headers);
106
107 $headers = array_replace_recursive(
108 $headers,
109 $this->is_connected() ? $this->generate_authentication_headers( $endpoint ) : []
110 );
111
112 $body = array_replace_recursive( $body, $this->get_site_info( $endpoint ) );
113
114 try {
115 if ( $file ) {
116 $boundary = wp_generate_password( 24, false );
117 $body = $this->get_upload_request_body( $body, $file, $boundary, $file_name );
118 // add content type header
119 $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
120 }
121 } catch ( Client_Exception $ce ) {
122 return new WP_Error( 500, $ce->getMessage() );
123 }
124
125 $response = $this->request(
126 $method,
127 $endpoint,
128 [
129 'timeout' => 100,
130 'headers' => $headers,
131 'body' => $body,
132 ]
133 );
134
135 return ( new Client_Response( $response ) )->handle();
136 }
137
138 private static function get_remote_url( $endpoint ): string {
139 $base_url = apply_filters( 'image_optimizer_client_get_base_url', self::BASE_URL );
140
141 return $base_url . $endpoint;
142 }
143
144 protected function is_connected(): bool {
145 return Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_connected();
146 }
147
148 protected function generate_authentication_headers( $endpoint ): array {
149
150 $connect_instance = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance;
151
152 if ( ! $connect_instance->get_is_connect_on_fly() ) {
153 $headers = [
154 'data' => base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
155 wp_json_encode( [ 'app' => 'library' ] )
156 ),
157 'access_token' => $connect_instance->get_access_token() ?? '',
158 'client_id' => $connect_instance->get_client_id() ?? '',
159 ];
160
161 if ( $connect_instance->is_activated() ) {
162 $headers['key'] = $connect_instance->get_activation_state() ?? '';
163 }
164 } else {
165 $headers = $this->add_bearer_token([
166 'x-elementor-apps-connect' => true,
167 ]);
168 }
169
170 $headers['endpoint'] = $endpoint;
171 $headers['x-elementor-apps'] = 'image-optimizer';
172
173 return $headers;
174 }
175
176 protected function request( $method, $endpoint, $args = [] ) {
177 $args['method'] = $method;
178
179 $response = wp_remote_request(
180 self::get_remote_url( $endpoint ),
181 $args
182 );
183
184 if ( is_wp_error( $response ) ) {
185 $message = $response->get_error_message();
186
187 return new WP_Error(
188 $response->get_error_code(),
189 is_array( $message ) ? join( ', ', $message ) : $message
190 );
191 }
192
193 $body = wp_remote_retrieve_body( $response );
194 $response_code = (int) wp_remote_retrieve_response_code( $response );
195
196 if ( ! $response_code ) {
197 return new WP_Error( 500, 'No Response' );
198 }
199
200 // Server sent a success message without content.
201 if ( 'null' === $body ) {
202 $body = true;
203 }
204
205 $body = json_decode( $body );
206
207 if ( false === $body ) {
208 return new WP_Error( 422, 'Wrong Server Response' );
209 }
210
211 if ( Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_is_connect_on_fly() ) {
212 // If the token is invalid, refresh it and try again once only.
213 if ( ! $this->refreshed && ! empty( $body->message ) && ( false !== strpos( $body->message, 'Invalid Token' ) ) ) {
214 Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->refresh_token();
215 $this->refreshed = true;
216 $args['headers'] = $this->add_bearer_token( $args['headers'] );
217 return $this->request( $method, $endpoint, $args );
218 }
219 }
220
221 if ( 200 !== $response_code ) {
222 // In case $as_array = true.
223 $message = $body->message ?? wp_remote_retrieve_response_message( $response );
224 $message = is_array( $message ) ? join( ', ', $message ) : $message;
225 $code = isset( $body->code ) ? (int) $body->code : $response_code;
226
227 return new WP_Error( $code, $message );
228 }
229
230 return $body;
231 }
232
233 public function add_bearer_token( $headers ) {
234 if ( $this->is_connected() ) {
235 $headers['Authorization'] = 'Bearer ' . Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_access_token();
236 }
237 return $headers;
238 }
239
240 /**
241 * get_upload_request_body
242 *
243 * @param array $body
244 * @param $file
245 * @param string $boundary
246 * @param string $file_name
247 *
248 * @return string
249 * @throws Client_Exception
250 */
251 private function get_upload_request_body( array $body, $file, string $boundary, string $file_name = '' ): string {
252 $payload = '';
253 // add all body fields as standard POST fields:
254 foreach ( $body as $name => $value ) {
255 $payload .= '--' . $boundary;
256 $payload .= "\r\n";
257 $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"' . "\r\n\r\n";
258 $payload .= $value;
259 $payload .= "\r\n";
260 }
261
262 if ( is_array( $file ) ) {
263 foreach ( $file as $key => $file_data ) {
264 $payload .= $this->get_file_payload( $file_data['name'], $file_data['type'], $file_data['path'], $boundary );
265 }
266 } else {
267 $image_mime = image_type_to_mime_type( exif_imagetype( $file ) );
268
269 if (
270 ! in_array( $image_mime, Image::get_supported_mime_types(), true ) &&
271 ( 'application/octet-stream' === $image_mime && 'avif' !== File_Utils::get_extension( $file ) )
272 ) {
273 throw new Client_Exception( "Unsupported mime type `$image_mime`" );
274 }
275
276 if ( empty( $file_name ) ) {
277 $file_name = basename( $file );
278 }
279
280 $payload .= $this->get_file_payload( $file_name, $image_mime, $file, $boundary );
281 }
282
283 $payload .= '--' . $boundary . '--';
284
285 return $payload;
286 }
287
288 /**
289 * get_file_payload
290 * @param string $filename
291 * @param string $file_type
292 * @param string $file_path
293 * @param string $boundary
294 * @return string
295 */
296 private function get_file_payload( string $filename, string $file_type, string $file_path, string $boundary ): string {
297 $name = $filename ?? basename( $file_path );
298 $mine_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type;
299 $payload = '';
300 // Upload the file
301 $payload .= '--' . $boundary;
302 $payload .= "\r\n";
303 $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"; filename="' . esc_attr( $name ) . '"' . "\r\n";
304 $payload .= 'Content-Type: ' . $mine_type . "\r\n";
305 $payload .= "\r\n";
306 $payload .= file_get_contents( $file_path );
307 $payload .= "\r\n";
308
309 return $payload;
310 }
311 }
312