PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.5.4
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.5.4
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.5.4, at classes/client/client.php

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