PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.6.1
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.6.1
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
image-optimization / classes / client / client.php

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

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