PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.13
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.13
4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 3.11.3 3.12.0 3.12.1 All 134 releases
← All changes | inc/rest.php +786 -350 2.5.64.2.13 View file →
@@ -6,8 +6,12 @@
6 6 * @copyright Copyright (c) 2017, Marius Cristea
7 7 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
8 8 */
9 9
10 +use Optimole\Sdk\Resource\ImageProperty\ResizeTypeProperty;
11 +use Optimole\Sdk\ValueObject\Position;
12 +use OptimoleWP\PageProfiler\Profile;
13 +
10 14 /**
11 15 * Class Optml_Rest
12 16 *
13 17 * @codeCoverageIgnore
@@ -12,8 +16,9 @@
12 16 *
13 17 * @codeCoverageIgnore
14 18 */
15 19 class Optml_Rest {
20 +
16 21 /**
17 22 * Rest api namespace.
18 23 *
19 24 * @var string Namespace.
@@ -20,302 +25,398 @@
20 25 */
21 26 private $namespace;
22 27
23 28 /**
29 + * Upload conflicts api.
30 + *
31 + * @var array{
32 + * service_routes: RestRouteMap,
33 + * image_routes: RestRouteMap,
34 + * media_cloud_routes: RestRouteMap,
35 + * conflict_routes: SimpleRouteMap,
36 + * cache_routes: SimpleRouteMap,
37 + * dam_routes: RestRouteMap,
38 + * notification_dismiss_routes: RestRouteMap,
39 + * optimization_routes: RestRouteMap
40 + * }
41 + */
42 + public static $rest_routes = [
43 + 'service_routes' => [
44 + 'update_option' => 'POST',
45 + 'request_update' => 'GET',
46 + 'connect' => [
47 + 'POST',
48 + 'args' => [
49 + 'api_key' => [
50 + 'type' => 'string',
51 + 'required' => true,
52 + ],
53 + ],
54 + ],
55 + 'select_application' => [
56 + 'POST',
57 + 'args' => [
58 + 'api_key' => [
59 + 'type' => 'string',
60 + 'required' => true,
61 + ],
62 + 'application' => [
63 + 'type' => 'string',
64 + 'required' => true,
65 + ],
66 + ],
67 + ],
68 + 'register_service' => [
69 + 'POST',
70 + 'args' => [
71 + 'email' => [
72 + 'type' => 'string',
73 + 'required' => true,
74 + ],
75 + ],
76 + ],
77 + 'disconnect' => 'GET',
78 + ],
79 + 'image_routes' => [
80 + 'poll_optimized_images' => 'GET',
81 + 'get_sample_rate' => 'POST',
82 + 'upload_onboard_images' => [
83 + 'POST',
84 + 'args' => [
85 + 'offset' => [
86 + 'type' => 'number',
87 + 'required' => false,
88 + 'default' => 0,
89 + ],
90 + ],
91 + ],
92 + ],
93 + 'media_cloud_routes' => [
94 + 'number_of_images_and_pages' => 'POST',
95 + 'clear_offload_errors' => 'GET',
96 + 'get_offload_conflicts' => 'GET',
97 + 'move_image' => [
98 + 'POST',
99 + 'args' => [
100 + 'id' => [
101 + 'type' => 'number',
102 + 'required' => true,
103 + ],
104 + 'action' => [
105 + 'type' => 'string',
106 + 'required' => true,
107 + ],
108 + ],
109 + 'permission_callback' => [ __CLASS__, 'can_move_image' ],
110 + ],
111 + ],
112 + 'conflict_routes' => [
113 + 'poll_conflicts' => 'GET',
114 + 'dismiss_conflict' => 'POST',
115 + ],
116 + 'cache_routes' => [
117 + 'clear_cache_request' => 'POST',
118 + ],
119 + 'dam_routes' => [
120 + 'insert_images' => [
121 + 'POST',
122 + 'args' => [
123 + 'images' => [
124 + 'type' => 'array',
125 + 'required' => true,
126 + ],
127 + ],
128 + 'permission_callback' => 'upload_files',
129 + ],
130 + ],
131 + 'notification_dismiss_routes' => [
132 + 'dismiss_notice' => [
133 + 'POST',
134 + 'args' => [
135 + 'key' => [
136 + 'type' => 'string',
137 + 'required' => true,
138 + ],
139 + ],
140 + ],
141 + ],
142 + 'optimization_routes' => [
143 + 'optimizations' => [
144 + 'POST',
145 + 'args' => [
146 + 'd' => [
147 + 'type' => 'integer',
148 + 'required' => true,
149 + ],
150 + 'a' => [
151 + 'type' => 'array',
152 + 'required' => true,
153 + ],
154 + 'u' => [
155 + 'type' => 'string',
156 + 'required' => true,
157 + ],
158 + ],
159 + 'permission_callback' => '__return_true',
160 + ],
161 + ],
162 + ];
163 +
164 + /**
24 165 * Optml_Rest constructor.
166 + *
167 + * @return void
25 168 */
26 169 public function __construct() {
27 170 $this->namespace = OPTML_NAMESPACE . '/v1';
28 - add_action( 'rest_api_init', array( $this, 'register' ) );
171 +
172 + add_action( 'rest_api_init', [ $this, 'register' ] );
29 173 }
30 174
31 175 /**
32 - * Register rest routes.
176 + * Method to register a specific REST route.
177 + *
178 + * @param string $route The route name.
179 + * @param string $method The route access method: GET, POST, POST_PUT_PATCH.
180 + * @param array $args Optional arguments for route parameters.
181 + * @param string|callable $permission_callback Permission callback function or capability.
182 + *
183 + * @phpstan-param RestArgs $args Optional arguments for route parameters.
184 + *
185 + * @throws \InvalidArgumentException If method is invalid.
186 + *
187 + * @return void
33 188 */
34 - public function register() {
189 + private function register_route( $route, $method = 'GET', $args = [], $permission_callback = 'manage_options' ) {
190 + if ( empty( $route ) ) {
191 + return;
192 + }
35 193
36 - $this->register_service_routes();
194 + $method_map = [
195 + 'GET' => \WP_REST_Server::READABLE,
196 + 'POST' => \WP_REST_Server::CREATABLE,
197 + 'POST_PUT_PATCH' => \WP_REST_Server::EDITABLE,
198 + ];
37 199
200 + if ( ! isset( $method_map[ $method ] ) ) {
201 + _doing_it_wrong(
202 + __METHOD__,
203 + sprintf( 'Invalid REST method: %s', esc_html( $method ) ),
204 + '1.0.0'
205 + );
206 + return;
207 + }
208 +
209 + $permission = is_callable( $permission_callback )
210 + ? $permission_callback
211 + : function () use ( $permission_callback ) {
212 + if ( ! is_string( $permission_callback ) ) {
213 + return false;
214 + }
215 + return current_user_can( $permission_callback );
216 + };
217 +
218 + $params = [
219 + 'methods' => $method_map[ $method ],
220 + 'permission_callback' => $permission,
221 + 'callback' => [ $this, $route ],
222 + ];
223 +
224 + if ( ! empty( $args ) ) {
225 + $params['args'] = $args;
226 + }
227 +
38 228 register_rest_route(
39 229 $this->namespace,
40 - '/update_option',
41 - array(
42 - array(
43 - 'methods' => \WP_REST_Server::CREATABLE,
44 - 'permission_callback' => function () {
45 - return current_user_can( 'manage_options' );
46 - },
47 - 'callback' => array( $this, 'update_option' ),
48 - ),
49 - )
230 + '/' . $route,
231 + [ $params ]
50 232 );
233 + }
51 234
52 - register_rest_route(
53 - $this->namespace,
54 - '/request_update',
55 - array(
56 - array(
57 - 'methods' => \WP_REST_Server::READABLE,
58 - 'permission_callback' => function () {
59 - return current_user_can( 'manage_options' );
60 - },
61 - 'callback' => array( $this, 'request_update' ),
62 - ),
63 - )
64 - );
65 - register_rest_route(
66 - $this->namespace,
67 - '/check_redirects',
68 - array(
69 - array(
70 - 'methods' => \WP_REST_Server::EDITABLE,
71 - 'permission_callback' => function () {
72 - return current_user_can( 'manage_options' );
73 - },
74 - 'callback' => array( $this, 'check_redirects' ),
75 - 'args' => array(
76 - 'images' => array(
77 - 'type' => 'Array',
78 - 'required' => true,
79 - ),
80 - ),
81 - ),
82 - )
83 - );
235 + /**
236 + * Register rest routes.
237 + *
238 + * @return void
239 + */
240 + public function register() {
84 241
242 + $this->register_service_routes();
243 +
85 244 $this->register_image_routes();
86 - $this->register_watermark_routes();
87 245 $this->register_conflict_routes();
88 246 $this->register_cache_routes();
247 + $this->register_media_offload_routes();
248 + $this->register_dam_routes();
249 + $this->register_notification_routes();
250 + $this->register_optimization_routes();
89 251 }
90 252
91 253 /**
92 254 * Method to register service specific routes.
255 + *
256 + * @return void
93 257 */
94 258 public function register_service_routes() {
95 - register_rest_route(
96 - $this->namespace,
97 - '/connect',
98 - array(
99 - array(
100 - 'methods' => \WP_REST_Server::CREATABLE,
101 - 'permission_callback' => function () {
102 - return current_user_can( 'manage_options' );
103 - },
104 - 'callback' => array( $this, 'connect' ),
105 - 'args' => array(
106 - 'api_key' => array(
107 - 'type' => 'string',
108 - 'required' => true,
109 - ),
110 - ),
111 - ),
112 - )
113 - );
114 - register_rest_route(
115 - $this->namespace,
116 - '/register',
117 - array(
118 - array(
119 - 'methods' => \WP_REST_Server::CREATABLE,
120 - 'permission_callback' => function () {
121 - return current_user_can( 'manage_options' );
122 - },
123 - 'callback' => array( $this, 'register_service' ),
124 - 'args' => array(
125 - 'email' => array(
126 - 'type' => 'string',
127 - 'required' => true,
128 - ),
129 - ),
130 - ),
131 - )
132 - );
133 - register_rest_route(
134 - $this->namespace,
135 - '/disconnect',
136 - array(
137 - array(
138 - 'methods' => \WP_REST_Server::READABLE,
139 - 'permission_callback' => function () {
140 - return current_user_can( 'manage_options' );
141 - },
142 - 'callback' => array( $this, 'disconnect' ),
143 - ),
144 - )
145 - );
259 + foreach ( self::$rest_routes['service_routes'] as $route => $details ) {
260 + if ( is_array( $details ) ) {
261 + $this->register_route( $route, $details[0], $details['args'] );
262 + } else {
263 + $this->register_route( $route, $details );
264 + }
265 + }
146 266 }
147 267
148 268 /**
149 269 * Method to register image specific routes.
270 + *
271 + * @return void
150 272 */
151 273 public function register_image_routes() {
152 - register_rest_route(
153 - $this->namespace,
154 - '/poll_optimized_images',
155 - array(
156 - array(
157 - 'methods' => \WP_REST_Server::READABLE,
158 - 'permission_callback' => function () {
159 - return current_user_can( 'manage_options' );
160 - },
161 - 'callback' => array( $this, 'poll_optimized_images' ),
162 - ),
163 - )
164 - );
165 - register_rest_route(
166 - $this->namespace,
167 - '/images-sample-rate',
168 - array(
169 - array(
170 - 'methods' => \WP_REST_Server::CREATABLE,
171 - 'permission_callback' => function () {
172 - return current_user_can( 'manage_options' );
173 - },
174 - 'callback' => array( $this, 'get_sample_rate' ),
175 - ),
176 - )
177 - );
274 + foreach ( self::$rest_routes['image_routes'] as $route => $details ) {
275 + if ( is_array( $details ) ) {
276 + $this->register_route( $route, $details[0], $details['args'] );
277 + } else {
278 + $this->register_route( $route, $details );
279 + }
280 + }
178 281 }
179 -
180 282 /**
181 - * Method to register watermark specific routes.
283 + * Method to register media offload specific routes.
284 + *
285 + * @return void
182 286 */
183 - public function register_watermark_routes() {
184 - register_rest_route(
185 - $this->namespace,
186 - '/poll_watermarks',
187 - array(
188 - array(
189 - 'methods' => \WP_REST_Server::READABLE,
190 - 'permission_callback' => function () {
191 - return current_user_can( 'manage_options' );
192 - },
193 - 'callback' => array( $this, 'poll_watermarks' ),
194 - ),
195 - )
196 - );
197 - register_rest_route(
198 - $this->namespace,
199 - '/add_watermark',
200 - array(
201 - array(
202 - 'methods' => \WP_REST_Server::CREATABLE,
203 - 'permission_callback' => function () {
204 - return current_user_can( 'manage_options' );
205 - },
206 - 'callback' => array( $this, 'add_watermark' ),
207 - ),
208 - )
209 - );
210 - register_rest_route(
211 - $this->namespace,
212 - '/remove_watermark',
213 - array(
214 - array(
215 - 'methods' => \WP_REST_Server::CREATABLE,
216 - 'permission_callback' => function () {
217 - return current_user_can( 'manage_options' );
218 - },
219 - 'callback' => array( $this, 'remove_watermark' ),
220 - ),
221 - )
222 - );
287 + public function register_media_offload_routes() {
288 + foreach ( self::$rest_routes['media_cloud_routes'] as $route => $details ) {
289 +
290 + $permission = isset( $details['permission_callback'] ) ? $details['permission_callback'] : 'manage_options';
291 + $args = isset( $details['args'] ) ? $details['args'] : [];
292 + $this->register_route( $route, is_array( $details ) ? $details[0] : $details, $args, $permission );
293 + }
223 294 }
224 295
296 +
225 297 /**
226 298 * Method to register conflicts specific routes.
299 + *
300 + * @return void
227 301 */
228 302 public function register_conflict_routes() {
229 - register_rest_route(
230 - $this->namespace,
231 - '/poll_conflicts',
232 - array(
233 - array(
234 - 'methods' => \WP_REST_Server::READABLE,
235 - 'permission_callback' => function () {
236 - return current_user_can( 'manage_options' );
237 - },
238 - 'callback' => array( $this, 'poll_conflicts' ),
239 - ),
240 - )
241 - );
242 - register_rest_route(
243 - $this->namespace,
244 - '/dismiss_conflict',
245 - array(
246 - array(
247 - 'methods' => \WP_REST_Server::CREATABLE,
248 - 'permission_callback' => function () {
249 - return current_user_can( 'manage_options' );
250 - },
251 - 'callback' => array( $this, 'dismiss_conflict' ),
252 - ),
253 - )
254 - );
303 + foreach ( self::$rest_routes['conflict_routes'] as $route => $details ) {
304 + $this->register_route( $route, $details );
305 + }
255 306 }
256 307
257 308 /**
258 309 * Method to register cache specific routes.
310 + *
311 + * @return void
259 312 */
260 313 public function register_cache_routes() {
261 - register_rest_route(
262 - $this->namespace,
263 - '/clear_cache',
264 - array(
265 - array(
266 - 'methods' => \WP_REST_Server::CREATABLE,
267 - 'permission_callback' => function () {
268 - return current_user_can( 'manage_options' );
269 - },
270 - 'callback' => array( $this, 'clear_cache_request' ),
271 - ),
272 - )
273 - );
314 + foreach ( self::$rest_routes['cache_routes'] as $route => $details ) {
315 + $this->register_route( $route, $details );
316 + }
274 317 }
275 318
276 319 /**
320 + * Register DAM routes.
321 + *
322 + * @return void
323 + */
324 + public function register_dam_routes() {
325 + foreach ( self::$rest_routes['dam_routes'] as $route => $details ) {
326 + $permission = isset( $details['permission_callback'] ) ? $details['permission_callback'] : 'manage_options';
327 + $args = isset( $details['args'] ) ? $details['args'] : [];
328 + $this->register_route( $route, $details[0], $args, $permission );
329 + }
330 + }
331 +
332 + /**
333 + * Register notification dismiss routes.
334 + *
335 + * @return void
336 + */
337 + public function register_notification_routes() {
338 + foreach ( self::$rest_routes['notification_dismiss_routes'] as $route => $details ) {
339 + $this->register_route( $route, $details[0], isset( $details['args'] ) ? $details['args'] : [] );
340 + }
341 + }
342 +
343 + /**
277 344 * Clear Cache request.
278 345 *
279 346 * @param WP_REST_Request $request clear cache rest request.
347 + * @phpstan-param WP_REST_Request<array{type?: string}> $request
280 348 *
281 349 * @return WP_Error|WP_REST_Response
282 350 */
283 351 public function clear_cache_request( WP_REST_Request $request ) {
284 352 $settings = new Optml_Settings();
285 - $token = $settings->get( 'cache_buster' );
286 - $request = new Optml_Api();
287 - $data = $request->get_cache_token( $token );
288 - if ( $data === false || is_wp_error( $data ) || empty( $data ) || ! isset( $data['token'] ) ) {
353 + $type = $request->get_param( 'type' );
354 + $response = $settings->clear_cache( $type );
355 +
356 + if ( is_wp_error( $response ) ) {
357 + wp_send_json_error( $response->get_error_message() );
358 + }
359 +
360 + return $this->response( $response, '200' );
361 + }
362 +
363 + /**
364 + * Connect to optimole service.
365 + *
366 + * @param WP_REST_Request $request connect rest request.
367 + * @phpstan-param WP_REST_Request<array{api_key: string, application?: string}> $request
368 + *
369 + * @return WP_Error|WP_REST_Response
370 + */
371 + public function connect( WP_REST_Request $request ) {
372 + $api_key = $request->get_param( 'api_key' );
373 + $original_request = $request;
374 + $request = new Optml_Api();
375 + $data = $request->connect( $api_key );
376 +
377 + if ( $data === false || is_wp_error( $data ) ) {
378 +
289 379 $extra = '';
290 380 if ( is_wp_error( $data ) ) {
381 + if ( $data->get_error_code() === 'domain_not_accessible' ) {
382 + return $this->response( $data->get_error_message(), 400 );
383 + }
291 384 /**
292 385 * Error from api.
293 386 *
294 387 * @var WP_Error $data Error object.
295 388 */
296 - $extra = sprintf( __( '. ERROR details: %s', 'optimole-wp' ), $data->get_error_message() );
389 + $extra = sprintf( /* translators: Error details */ __( '. ERROR details: %s', 'optimole-wp' ), $data->get_error_message() );
297 390 }
298 - wp_send_json_error( __( 'Can not get new token from Optimole service', 'optimole-wp' ) . $extra );
391 + return $this->response( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra, 400 );
299 392 }
393 + $settings = new Optml_Settings();
394 + $settings->update( 'api_key', $api_key );
300 395
301 - set_transient( 'optml_cache_lock', 'yes', 5 * MINUTE_IN_SECONDS );
302 - $settings->update( 'cache_buster', $data['token'] );
396 + if ( isset( $data['extra_visits'] ) ) {
397 + $settings->update_frontend_banner_from_remote( $data['extra_visits'] );
398 + }
303 399
304 - return $this->response( $data['token'], '200' );
400 + if ( $data['app_count'] === 1 ) {
401 + return $this->select_application( $original_request );
402 + }
403 + return $this->response( $data );
305 404 }
306 405
307 406 /**
308 - * Connect to optimole service.
407 + * Select application.
309 408 *
310 - * @param WP_REST_Request $request connect rest request.
409 + * @param WP_REST_Request $request Rest request.
410 + * @phpstan-param WP_REST_Request<array{api_key: string, application?: string}> $request
311 411 *
312 - * @return WP_Error|WP_REST_Response
412 + * @return WP_REST_Response
313 413 */
314 - public function connect( WP_REST_Request $request ) {
414 + public function select_application( WP_REST_Request $request ) {
315 415 $api_key = $request->get_param( 'api_key' );
416 + $application = $request->get_param( 'application' );
316 417 $request = new Optml_Api();
317 - $data = $request->get_user_data( $api_key );
418 + $data = $request->get_user_data( $api_key, $application );
318 419 if ( $data === false || is_wp_error( $data ) ) {
319 420 $extra = '';
320 421 if ( is_wp_error( $data ) ) {
321 422 /**
@@ -322,16 +423,14 @@
322 423 * Error from api.
323 424 *
324 425 * @var WP_Error $data Error object.
325 426 */
326 - $extra = sprintf( __( '. ERROR details: %s', 'optimole-wp' ), $data->get_error_message() );
427 + $extra = sprintf( /* translators: Error details */ __( '. ERROR details: %s', 'optimole-wp' ), $data->get_error_message() );
327 428 }
328 429 wp_send_json_error( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra );
329 430 }
330 431 $settings = new Optml_Settings();
331 432 $settings->update( 'service_data', $data );
332 - $settings->update( 'api_key', $api_key );
333 -
334 433 return $this->response( $data );
335 434 }
336 435
337 436 /**
@@ -336,14 +435,15 @@
336 435
337 436 /**
338 437 * Wrapper for api response.
339 438 *
340 - * @param mixed $data data from api.
439 + * @param mixed $data data from api.
440 + * @param string|int $code Response code.
341 441 *
342 442 * @return WP_REST_Response
343 443 */
344 444 private function response( $data, $code = 'success' ) {
345 - return new WP_REST_Response( array( 'data' => $data, 'code' => $code ), 200 );
445 + return new WP_REST_Response( [ 'data' => $data, 'code' => $code ], 200 );
346 446 }
347 447
348 448 /**
349 449 * Connect to optimole service.
@@ -348,8 +448,9 @@
348 448 /**
349 449 * Connect to optimole service.
350 450 *
351 451 * @param WP_REST_Request $request connect rest request.
452 + * @phpstan-param WP_REST_Request<array{email: string, auto_connect?: string}> $request
352 453 *
353 454 * @return WP_Error|WP_REST_Response
354 455 */
355 456 public function register_service( WP_REST_Request $request ) {
@@ -354,22 +455,74 @@
354 455 */
355 456 public function register_service( WP_REST_Request $request ) {
356 457 $email = $request->get_param( 'email' );
357 458 $api = new Optml_Api();
459 +
358 460 $user = $api->create_account( $email );
359 - if ( $user === false ) {
461 +
462 + $auto_connect = $request->get_param( 'auto_connect' );
463 +
464 + if ( ! empty( $auto_connect ) && $auto_connect === 'true' ) {
465 + delete_option( Optml_Settings::OPTML_USER_EMAIL );
466 + }
467 + if ( $user === false || is_wp_error( $user ) ) {
468 + if ( $user->get_error_code() === 'domain_not_accessible' ) {
469 + return new WP_REST_Response(
470 + [
471 + 'data' => null,
472 + 'message' => $user->get_error_message(),
473 + 'code' => 'domain_not_accessible',
474 + ],
475 + 200
476 + );
477 + }
478 +
360 479 return new WP_REST_Response(
361 - array(
480 + [
362 481 'data' => null,
363 - 'message' => __( 'Error creating account.', 'optimole-wp' ),
482 + 'message' => __( 'Error creating account.', 'optimole-wp' ) . ' ' . $user->get_error_message(),
364 483 'code' => 'error',
365 - ),
484 + ],
366 485 200
367 486 );
487 + }
488 + if ( $user === 'email_registered' ) {
489 + return new WP_REST_Response(
490 + [
491 + 'data' => null,
492 + 'message' => __( 'Error: This email is already registered. Please choose another one.', 'optimole-wp' ),
493 + 'code' => 'email_registered',
494 + ],
495 + 200
496 + );
368 497
369 498 }
370 499
371 - return $this->response( $user );
500 + if ( $user === 'site_exists' ) {
501 + return new WP_REST_Response(
502 + [
503 + 'data' => null,
504 + 'message' => sprintf( /* translators: 1 start anchor tag to register page, 2 is ending anchor tag. */ __( 'Error: This site has been previously registered. You can login to your account from %1$shere%2$s', 'optimole-wp' ), '<a href="' . esc_url( tsdk_translate_link( 'https://dashboard.optimole.com/login', 'query' ) ) . '" target="_blank"> ', '</a>' ),
505 + 'code' => 'site_exists',
506 + ],
507 + 200
508 + );
509 + }
510 +
511 + $user_data = $user['res'];
512 +
513 + $settings = new Optml_Settings();
514 + $settings->update( 'api_key', $user_data['api_key'] );
515 +
516 + if ( $user_data['app_count'] === 1 ) {
517 + $settings->update( 'service_data', $user_data );
518 + }
519 +
520 + if ( isset( $user_data['extra_visits'] ) ) {
521 + $settings->update_frontend_banner_from_remote( $user_data['extra_visits'] );
522 + }
523 +
524 + return $this->response( $user_data );
372 525 }
373 526
374 527 /**
375 528 * Return image samples.
@@ -374,8 +527,9 @@
374 527 /**
375 528 * Return image samples.
376 529 *
377 530 * @param WP_REST_Request $request Rest request.
531 + * @phpstan-param WP_REST_Request<array{quality?: int, force?: string}> $request
378 532 *
379 533 * @return WP_REST_Response Image urls.
380 534 */
381 535 public function get_sample_rate( WP_REST_Request $request ) {
@@ -385,9 +539,9 @@
385 539 if ( $image_sample === false || $request->get_param( 'force' ) === 'yes' ) {
386 540 $image_sample = $this->fetch_sample_image();
387 541 set_transient( 'optimole_sample_image', $image_sample );
388 542 }
389 - $image = array( 'id' => $image_sample['id'] );
543 + $image = [ 'id' => $image_sample['id'] ];
390 544
391 545 $image['original'] = $image_sample['url'];
392 546
393 547 remove_filter( 'optml_dont_replace_url', '__return_true' );
@@ -394,23 +548,23 @@
394 548
395 549 $image['optimized'] = apply_filters(
396 550 'optml_replace_image',
397 551 $image['original'],
398 - array(
552 + [
399 553 'width' => $image_sample['width'],
400 554 'height' => $image_sample['height'],
401 555 'quality' => $request->get_param( 'quality' ),
402 - )
556 + ]
403 557 );
404 558
405 559 $optimized = wp_remote_get(
406 560 $image['optimized'],
407 - array(
561 + [
408 562 'timeout' => 10,
409 - 'headers' => array(
563 + 'headers' => [
410 564 'Accept' => 'text/html,application/xhtml+xml,image/webp,image/apng ',
411 - ),
412 - )
565 + ],
566 + ]
413 567 );
414 568
415 569 $original = wp_remote_get( $image['original'] );
416 570
@@ -420,15 +574,75 @@
420 574 return $this->response( $image );
421 575 }
422 576
423 577 /**
578 + * Crawl & upload initial load.
579 + *
580 + * @param WP_REST_Request $request Rest request.
581 + * @phpstan-param WP_REST_Request<array{offset?: int}> $request
582 + *
583 + * @return WP_REST_Response If there are more posts left to receive.
584 + */
585 + public function upload_onboard_images( WP_REST_Request $request ) {
586 + $offset = absint( $request->get_param( 'offset' ) );
587 +
588 + // Arguments for get_posts function
589 + $args = [
590 + 'post_type' => 'attachment',
591 + 'post_mime_type' => 'image',
592 + 'post_status' => 'inherit',
593 + 'posts_per_page' => 100,
594 + 'offset' => $offset,
595 + 'fields' => 'ids',
596 + 'no_found_rows' => true,
597 + 'update_post_meta_cache' => false,
598 + 'update_post_term_cache' => false,
599 + 'orderby' => [
600 + 'parent' => 'DESC',
601 + ],
602 + ];
603 +
604 + // Initialize an array to store the image URLs
605 + $image_urls = [];
606 +
607 + add_filter( 'optml_dont_replace_url', '__return_true' );
608 +
609 + // If site has a logo, get the logo url if offset is 0, ie first request
610 + if ( $offset === 0 ) {
611 + $custom_logo_id = get_theme_mod( 'custom_logo' );
612 +
613 + if ( $custom_logo_id ) {
614 + $image_urls[] = wp_get_attachment_url( $custom_logo_id );
615 + }
616 + }
617 +
618 + $query = new \WP_Query( $args );
619 +
620 + if ( $query->have_posts() ) {
621 + $image_ids = $query->get_posts();
622 + $image_urls = array_map( 'wp_get_attachment_url', $image_ids );
623 + }
624 + remove_filter( 'optml_dont_replace_url', '__return_true' );
625 +
626 + if ( count( $image_urls ) === 0 ) {
627 + return $this->response( true );
628 + }
629 +
630 + $api = new Optml_Api();
631 + $api->call_onboard_api( $image_urls );
632 +
633 + // Check if image_url array has at least 100 items, if not, we have reached the end of the images
634 + return $this->response( count( $image_urls ) < 100 ? true : false );
635 + }
636 +
637 + /**
424 638 * Return sample image data.
425 639 *
426 - * @return array Image data.
640 + * @return array{url: string, width: string|int, height: string|int, id: int} Image data.
427 641 */
428 642 private function fetch_sample_image() {
429 - $accepted_mimes = array( 'image/jpeg' );
430 - $args = array(
643 + $accepted_mimes = [ 'image/jpeg' ];
644 + $args = [
431 645 'post_type' => 'attachment',
432 646 'post_status' => 'any',
433 647 'number' => '5',
434 648 'no_found_rows' => true,
@@ -433,21 +647,21 @@
433 647 'number' => '5',
434 648 'no_found_rows' => true,
435 649 'fields' => 'ids',
436 650 'post_mime_type' => $accepted_mimes,
437 - 'post_parent__not_in' => array( 0 ),
438 - );
651 + 'post_parent__not_in' => [ 0 ],
652 + ];
439 653 $image_result = new WP_Query( $args );
440 654 if ( empty( $image_result->posts ) ) {
441 655 $rand_id = rand( 1, 3 );
442 656 $original_image_url = OPTML_URL . 'assets/img/' . $rand_id . '.jpg';
443 657
444 - return array(
658 + return [
445 659 'url' => $original_image_url,
446 660 'width' => '700',
447 661 'height' => '465',
448 662 'id' => - 1,
449 - );
663 + ];
450 664 }
451 665 $attachment_id = $image_result->posts[ array_rand( $image_result->posts, 1 ) ];
452 666
453 667 $original_image_url = wp_get_attachment_image_url( $attachment_id, 'full' );
@@ -461,21 +675,22 @@
461 675 $width = $metadata['sizes'][ $size ]['width'];
462 676 $height = $metadata['sizes'][ $size ]['height'];
463 677 }
464 678
465 - return array(
679 + return [
466 680 'url' => $original_image_url,
467 681 'id' => $attachment_id,
468 682 'width' => $width,
469 683 'height' => $height,
470 - );
684 + ];
471 685 }
472 686
473 687 /**
474 688 * Disconnect from optimole service.
475 689 *
476 - * @SuppressWarnings(PHPMD.UnusedFormalParameter)
477 690 * @param WP_REST_Request $request disconnect rest request.
691 + *
692 + * @return void
478 693 */
479 694 public function disconnect( WP_REST_Request $request ) {
480 695 $settings = new Optml_Settings();
481 696 $settings->reset();
@@ -485,8 +700,9 @@
485 700 /**
486 701 * Get optimized images from API.
487 702 *
488 703 * @param WP_REST_Request $request rest request.
704 + * @phpstan-param WP_REST_Request<array{api_key?: string}> $request
489 705 *
490 706 * @return WP_REST_Response
491 707 */
492 708 public function poll_optimized_images( WP_REST_Request $request ) {
@@ -492,70 +708,34 @@
492 708 public function poll_optimized_images( WP_REST_Request $request ) {
493 709 $api_key = $request->get_param( 'api_key' );
494 710 $request = new Optml_Api();
495 711 $images = $request->get_optimized_images( $api_key );
496 - if ( ! isset( $images['list'] ) || empty( $images['list'] ) ) {
497 - return $this->response( array() );
712 + if ( is_wp_error( $images ) || ! is_array( $images ) || empty( $images['list'] ) ) {
713 + return $this->response( [] );
498 714 }
499 715
500 716 $final_images = array_splice( $images['list'], 0, 10 );
501 717
502 - return $this->response( $final_images );
503 - }
504 -
505 - /**
506 - * Get watermarks from API.
507 - *
508 - * @param WP_REST_Request $request rest request.
509 - *
510 - * @return WP_REST_Response
511 - */
512 - public function poll_watermarks( WP_REST_Request $request ) {
513 - $api_key = $request->get_param( 'api_key' );
514 - $request = new Optml_Api();
515 - $watermarks = $request->get_watermarks( $api_key );
516 - if ( ! isset( $watermarks['watermarks'] ) || empty( $watermarks['watermarks'] ) ) {
517 - return $this->response( array() );
718 + foreach ( $final_images as $index => $value ) {
719 + $final_images[ $index ]['url'] = Optml_Media_Offload::instance()->get_media_optimized_url(
720 + $value['url'],
721 + strtolower( $value['key'] ),
722 + 140,
723 + 140,
724 + [
725 + 'type' => ResizeTypeProperty::FILL,
726 + 'enlarge' => false,
727 + 'gravity' => Position::CENTER,
728 + ]
729 + );
730 + unset( $final_images[ $index ]['key'] );
518 731 }
519 - $final_images = array_splice( $watermarks['watermarks'], 0, 10 );
520 732
521 733 return $this->response( $final_images );
522 734 }
523 735
524 - /**
525 - * Add watermark.
526 - *
527 - * @param WP_REST_Request $request rest request.
528 - *
529 - * @return WP_REST_Response
530 - */
531 - public function add_watermark( WP_REST_Request $request ) {
532 - $file = $request->get_file_params();
533 - $request = new Optml_Api();
534 - $response = $request->add_watermark( $file );
535 - if ( $response === false ) {
536 - return $this->response( __( 'Error uploading image. Please try again.', 'optimole-wp' ), 'error' );
537 - }
538 736
539 - return $this->response( __( 'Watermark image uploaded succesfully ! ', 'optimole-wp' ) );
540 - }
541 -
542 737 /**
543 - * Remove watermark.
544 - *
545 - * @param WP_REST_Request $request rest request.
546 - *
547 - * @return WP_REST_Response
548 - */
549 - public function remove_watermark( WP_REST_Request $request ) {
550 - $post_id = $request->get_param( 'postID' );
551 - $api_key = $request->get_param( 'api_key' );
552 - $request = new Optml_Api();
553 -
554 - return $this->response( $request->remove_watermark( $post_id, $api_key ) );
555 - }
556 -
557 - /**
558 738 * Get conflicts from API.
559 739 *
560 740 * @param WP_REST_Request $request rest request.
561 741 *
@@ -561,16 +741,16 @@
561 741 *
562 742 * @return WP_REST_Response
563 743 */
564 744 public function poll_conflicts( WP_REST_Request $request ) {
565 - $conflicts_to_register = apply_filters( 'optml_register_conflicts', array() );
745 + $conflicts_to_register = apply_filters( 'optml_register_conflicts', [] );
566 746 $manager = new Optml_Conflict_Manager( $conflicts_to_register );
567 747
568 748 return $this->response(
569 - array(
749 + [
570 750 'count' => $manager->get_conflict_count(),
571 751 'conflicts' => $manager->get_conflict_list(),
572 - )
752 + ]
573 753 );
574 754 }
575 755
576 756 /**
@@ -576,22 +756,23 @@
576 756 /**
577 757 * Dismiss conflict.
578 758 *
579 759 * @param WP_REST_Request $request rest request.
760 + * @phpstan-param WP_REST_Request<array{conflictID?: string}> $request
580 761 *
581 762 * @return WP_REST_Response
582 763 */
583 764 public function dismiss_conflict( WP_REST_Request $request ) {
584 765 $conflict_id = $request->get_param( 'conflictID' );
585 - $conflicts_to_register = apply_filters( 'optml_register_conflicts', array() );
766 + $conflicts_to_register = apply_filters( 'optml_register_conflicts', [] );
586 767 $manager = new Optml_Conflict_Manager( $conflicts_to_register );
587 768 $manager->dismiss_conflict( $conflict_id );
588 769
589 770 return $this->response(
590 - array(
771 + [
591 772 'count' => $manager->get_conflict_count(),
592 773 'conflicts' => $manager->get_conflict_list(),
593 - )
774 + ]
594 775 );
595 776 }
596 777
597 778 /**
@@ -603,10 +784,13 @@
603 784 */
604 785 public function request_update( WP_REST_Request $request ) {
605 786 do_action( 'optml_daily_sync' );
606 787 $settings = new Optml_Settings();
607 -
608 - return $this->response( $settings->get( 'service_data' ) );
788 + $service_data = $settings->get( 'service_data' );
789 + if ( empty( $service_data ) ) {
790 + return $this->response( '', 'disconnected' );
791 + }
792 + return $this->response( $service_data );
609 793 }
610 794
611 795 /**
612 796 * Update options method.
@@ -611,8 +795,9 @@
611 795 /**
612 796 * Update options method.
613 797 *
614 798 * @param WP_REST_Request $request option update rest request.
799 + * @phpstan-param WP_REST_Request<array{settings?: array<string, mixed>}> $request
615 800 *
616 801 * @return WP_REST_Response
617 802 */
618 803 public function update_option( WP_REST_Request $request ) {
@@ -628,65 +813,316 @@
628 813 return $this->response( $sanitized );
629 814 }
630 815
631 816 /**
632 - * Update options method.
817 + * Get total number of images.
633 818 *
634 - * @param WP_REST_Request $request option update rest request.
819 + * @param WP_REST_Request $request rest request object.
820 + * @phpstan-param WP_REST_Request<array{action?: string, refresh?: bool}> $request
635 821 *
636 822 * @return WP_REST_Response
637 823 */
638 - public function check_redirects( WP_REST_Request $request ) {
639 - if ( empty( $request->get_param( 'images' ) ) ) {
640 - return $this->response( __( 'No images available on the current page.' ), 'noImagesFound' );
824 + public function number_of_images_and_pages( WP_REST_Request $request ) {
825 + $action = 'offload_images';
826 + $refresh = false;
827 +
828 + if ( ! empty( $request->get_param( 'action' ) ) ) {
829 + $action = $request->get_param( 'action' );
641 830 }
642 - // 'ok' if no issues found, 'log' is there are issues we need to notify, 'deactivated' if the user's account is disabled
643 - $status = 'ok';
644 - $result = '';
645 - foreach ( $request->get_param( 'images' ) as $domain => $value ) {
646 - $args = array(
647 - 'method' => 'GET',
648 - 'redirection' => 0,
649 - );
650 - $processed_images = 0;
651 - if ( isset( $value['src'] ) ) {
652 - $processed_images = count( $value['src'] );
831 +
832 + if ( ! empty( $request->get_param( 'refresh' ) ) ) {
833 + $refresh = $request->get_param( 'refresh' );
834 + }
835 +
836 + return $this->response( Optml_Media_Offload::move_images( $action, $refresh ) );
837 + }
838 +
839 + /**
840 + * Clear the offload errors from previous offload attempts.
841 + *
842 + * @param WP_REST_Request $request Rest request object.
843 + *
844 + * @return WP_REST_Response
845 + */
846 + public function clear_offload_errors( WP_REST_Request $request ) {
847 + $action = $request->get_param( 'action' );
848 +
849 + if ( 'rollback_images' === $action ) {
850 + $delete_count = Optml_Media_Offload::clear_rollback_errors_meta();
851 + } else {
852 + $delete_count = Optml_Media_Offload::clear_offload_errors_meta();
853 + }
854 +
855 + return $this->response( [ 'success' => $delete_count ] );
856 + }
857 +
858 + /**
859 + * Get conflicts list.
860 + *
861 + * @param WP_REST_Request $request rest request object.
862 + *
863 + * @return WP_REST_Response
864 + */
865 + public function get_offload_conflicts( WP_REST_Request $request ) {
866 + $request = new Optml_Api();
867 + $decoded_list = $request->get_offload_conflicts();
868 + $active_conflicts = [];
869 + if ( isset( $decoded_list['plugins'] ) ) {
870 + foreach ( $decoded_list['plugins'] as $slug ) {
871 + if ( is_plugin_active( $slug ) ) {
872 + $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug );
873 + if ( ! empty( $plugin_data['Name'] ) ) {
874 + $active_conflicts[] = $plugin_data['Name'];
875 + }
876 + }
653 877 }
654 - if ( isset( $value['ignoredUrls'] ) && $value['ignoredUrls'] > $processed_images ) {
655 - $result .= '<li>❌ ' . sprintf( __( 'The images from: %1$s are not optimized by Optimole. If you would like to do so, you can follow this: %2$sWhy Optimole does not optimize all the images from my site?%3$s.', 'optimole-wp' ), $domain, '<a target="_blank" href="https://docs.optimole.com/article/1290-how-to-optimize-images-using-optimole-from-my-domain">', '</a>' ) . '</li>';
656 - $status = 'log';
657 - continue;
878 + }
879 +
880 + return $this->response( $active_conflicts );
881 + }
882 +
883 + /**
884 + * Insert images request.
885 + *
886 + * @param WP_REST_Request $request insert images rest request.
887 + * @phpstan-param WP_REST_Request<array{images?: array<mixed>}> $request
888 + *
889 + * @return WP_REST_Response
890 + */
891 + public function insert_images( WP_REST_Request $request ) {
892 + $images = $request->get_param( 'images' );
893 +
894 + if ( ! is_array( $images ) || empty( $images ) ) {
895 + return $this->response( [ 'error' => 'No images found' ] );
896 + }
897 +
898 + $insert = Optml_Main::instance()->dam->insert_attachments( $images );
899 +
900 + return $this->response( $insert );
901 + }
902 +
903 + /**
904 + * Dismiss a notification (set the notification key to 'yes').
905 + *
906 + * @param WP_REST_Request $request the incoming request.
907 + * @phpstan-param WP_REST_Request<array{key?: string}> $request
908 + *
909 + * @return WP_REST_Response
910 + */
911 + public function dismiss_notice( WP_REST_Request $request ) {
912 + $key = $request->get_param( 'key' );
913 +
914 + if ( empty( $key ) ) {
915 + return $this->response( [ 'error' => 'Invalid key' ], 'error' );
916 + }
917 +
918 + $result = update_option( $key, 'yes' );
919 +
920 + if ( ! $result ) {
921 + return $this->response( [ 'error' => 'Could not dismiss notice' ], 'error' );
922 + }
923 +
924 + return $this->response( [ 'success' => 'Notice dismissed' ] );
925 + }
926 +
927 + /**
928 + * Store optimization data.
929 + *
930 + * @param WP_REST_Request $request Rest request.
931 + * @phpstan-param WP_REST_Request<array{d: int, a: array<mixed>, u: string, t?: int, h?: string, pu?: string, b?: array<string, array<string, list<string>>>, l?: array{i?: string, s?: string, u?: list<string>}, m?: array<int, array{w: int, h: int}>, s?: array<int, list<array{w: int, h: int, d: int, s: string, b: int}>>, c?: array<int, bool>}> $request
932 + *
933 + * @return WP_REST_Response
934 + */
935 + public function optimizations( WP_REST_Request $request ) {
936 + $settings = new Optml_Settings();
937 + if ( ! Optml_Manager::should_load_profiler() ) {
938 + return $this->response( 'Optimization is not enabled', 'error' );
939 + }
940 + $time = $request->get_param( 't' );
941 + $hmac = $request->get_param( 'h' );
942 + $current_url = $request->get_param( 'pu' );
943 + if ( empty( $time ) || empty( $hmac ) ) {
944 + return $this->response( 'Missing required parameters', 'error' );
945 + }
946 +
947 + $device_type = (int) $request->get_param( 'd' );
948 + $above_fold_images = $request->get_param( 'a' );
949 + $url = $request->get_param( 'u' );
950 + if ( $time < time() - 300 ) {
951 + return $this->response( 'Invalid Signature.', 'error' );
952 + }
953 + if ( wp_hash( $url . $time . $current_url, 'nonce' ) !== $hmac ) {
954 + return $this->response( 'Invalid Signature.', 'error' );
955 + }
956 + $bg_selectors = $request->get_param( 'b' );
957 + $lcp_data = $request->get_param( 'l' );
958 + $crop_status = $request->get_param( 'c' );
959 + $origin = $request->get_header( 'origin' );
960 + if ( empty( $origin ) || ! is_allowed_http_origin( $origin ) ) {
961 + return $this->response( 'Invalid origin', 'error' );
962 + }
963 + if ( empty( $device_type ) || empty( $url ) || ! is_array( $above_fold_images ) ) {
964 + return $this->response( 'Missing required parameters', 'error' );
965 + }
966 + // save just the first 6 images, see https://github.com/Codeinwp/optimole-service/issues/1588#issuecomment-3357110865
967 + $above_fold_images = array_slice( $above_fold_images, 0, 6 );
968 + $above_fold_images = array_values( array_map( 'intval', array_filter( $above_fold_images, 'is_numeric' ) ) );
969 + if ( count( $bg_selectors ) > 100 ) {
970 + return $this->response( 'Background selectors limit exceeded', 'error' );
971 + }
972 + if ( $url === Profile::PLACEHOLDER ) {
973 + return $this->response( 'Missing profile parameters', 'error' );
974 + }
975 +
976 + $current_selectors = array_values( Optml_Lazyload_Replacer::get_background_lazyload_selectors() );
977 + $sanitized_selectors = [];
978 + foreach ( $bg_selectors as $selector => $above_fold_bg_selectors ) {
979 + if ( ! in_array( $selector, $current_selectors, true ) ) {
980 + return $this->response( 'Invalid background selector', 'error' );
658 981 }
659 -
660 - if ( $processed_images > 0 ) {
661 - $response = wp_remote_get( $value['src'][ rand( 0, $processed_images - 1 ) ], $args );
662 - if ( is_array( $response ) && ! is_wp_error( $response ) ) {
663 - $headers = $response['headers']; // array of http header lines
664 - $status_code = $response['response']['code'];
665 - if ( $status_code === 301 ) {
666 - $status = 'deactivated';
667 - $result = '<li>❌ ' . sprintf( __( 'Your account is currently disabled due to exceeding quota and Optimole is no longer able to optimize the images. In order to fix this you will need to %1$supgrade%2$s.', 'optimole-wp' ), '<a target="_blank" href="https://optimole.com/pricing">', '</a>' ) . '</li>';
668 - break;
982 + if ( count( $above_fold_bg_selectors ) > 100 ) {
983 + return $this->response( 'Above fold background selectors limit exceeded', 'error' );
984 + }
985 + $selector = strip_tags( $selector );
986 + $sanitized_selectors[ $selector ] = [];
987 + foreach ( $above_fold_bg_selectors as $above_fold_bg_selector => $bg_urls ) {
988 + if ( count( $bg_urls ) > 3 ) {
989 + return $this->response( 'Background URLs limit exceeded', 'error' );
990 + }
991 + $sanitized_selectors[ $selector ][ strip_tags( $above_fold_bg_selector ) ] = array_filter(
992 + array_map( 'sanitize_url', array_values( $bg_urls ) ),
993 + function ( $url ) {
994 + // we ignore urls that are not from our service
995 + return strpos( $url, Optml_Config::$service_url ) === 0;
669 996 }
670 - if ( $status_code === 302 ) {
671 - if ( isset( $headers['x-redirect-o'] ) ) {
672 - $optimole_code = (int) $headers['x-redirect-o'];
673 - if ( $optimole_code === 1 ) {
674 - $status = 'log';
675 - $result .= '<li>❌ ' . sprintf( __( 'The domain: %1$s is not allowed to optimize images using your Optimole account. You can add this to the allowed list %2$shere%3$s.', 'optimole-wp' ), '<b>' . $domain . '</b>', '<a target="_blank" href="https://dashboard.optimole.com/whitelist">', '</a>' ) . '</li>';
676 - }
677 - if ( $optimole_code === 4 ) {
678 - $status = 'log';
679 - $result .= '<li>❌ ' . sprintf( __( 'We are not able to download the images from %1$s. Please check %2$sthis%3$s document for a more advanced guide on how to solve this. ', 'optimole-wp' ), '<b>' . $domain . '</b>', '<a target="_blank" href="https://docs.optimole.com/article/1291-why-optimole-is-not-able-to-download-the-images-from-my-site">', '</a>' ) . '<br />' . '</li>';
680 - }
681 - }
997 + );
998 + }
999 + }
1000 + $missing_dimensions = $request->get_param( 'm' );
1001 + $sanitized_missing_dimensions = [];
1002 + if ( ! empty( $missing_dimensions ) ) {
1003 + foreach ( $missing_dimensions as $id => $dimension ) {
1004 + $sanitized_missing_dimensions[ intval( $id ) ] = [ 'w' => intval( $dimension['w'] ), 'h' => intval( $dimension['h'] ) ];
1005 + }
1006 + }
1007 + $missing_srcsets = $request->get_param( 's' );
1008 + $sanitized_missing_srcsets = [];
1009 + if ( ! empty( $missing_srcsets ) ) {
1010 + foreach ( $missing_srcsets as $id => $srcset ) {
1011 + foreach ( $srcset as $size ) {
1012 + if ( ! isset( $size['w'], $size['h'], $size['d'], $size['s'], $size['b'] ) ) {
1013 + continue;
682 1014 }
1015 + $sanitized_missing_srcsets[ intval( $id ) ][ intval( $size['w'] ) ] = [ 'h' => intval( $size['h'] ), 'w' => intval( $size['w'] ), 'd' => intval( $size['d'] ), 's' => sanitize_text_field( $size['s'] ), 'b' => intval( $size['b'] ) ];
683 1016 }
684 1017 }
685 1018 }
686 - if ( $result === '' ) {
687 - $result = __( 'No issues detected, everything is running smoothly.', 'optimole-wp' );
1019 + $sanitized_crop_status = [];
1020 + if ( ! empty( $crop_status ) ) {
1021 + foreach ( $crop_status as $id => $crop ) {
1022 + $sanitized_crop_status[ intval( $id ) ] = (bool) $crop;
1023 + }
688 1024 }
1025 + $sanitized_lcp_data = [];
1026 + if ( ! empty( $lcp_data ) ) {
1027 + $sanitized_lcp_data['imageId'] = sanitize_text_field( $lcp_data['i'] ?? '' );
1028 + $sanitized_lcp_data['bgSelector'] = sanitize_text_field( $lcp_data['s'] ?? '' );
1029 + if ( count( $lcp_data['u'] ?? [] ) > 3 ) {
1030 + return $this->response( 'LCP Background URLs limit exceeded', 'error' );
1031 + }
1032 + $sanitized_lcp_data['bgUrls'] = array_filter(
1033 + array_map( 'sanitize_url', array_values( $lcp_data['u'] ?? [] ) ),
1034 + function ( $url ) {
1035 + // we ignore urls that are not from our service
1036 + return strpos( $url, Optml_Config::$service_url ) === 0;
1037 + }
1038 + );
1039 + $sanitized_lcp_data['type'] = empty( $sanitized_lcp_data['imageId'] ) ? 'bg' : 'img';
1040 + }
689 1041
690 - return $this->response( '<ul>' . $result . '</ul>', $status );
1042 + if ( OPTML_DEBUG ) {
1043 + do_action( 'optml_log', 'Storing profile data: ' . $url . ' - ' . $device_type . ' - ' . print_r( $above_fold_images, true ) . print_r( $sanitized_selectors, true ) . print_r( $sanitized_lcp_data, true ) . print_r( $sanitized_missing_dimensions, true ) . print_r( $sanitized_missing_srcsets, true ) . print_r( $sanitized_crop_status, true ) );
1044 + }
1045 + $profile = new Profile();
1046 + $profile->store( $url, $device_type, $above_fold_images, $sanitized_selectors, $sanitized_lcp_data, $sanitized_missing_dimensions, $sanitized_missing_srcsets, $sanitized_crop_status );
1047 +
1048 + if ( $profile->exists_all( $url ) ) {
1049 + /**
1050 + * Clear cache when storing profile data.
1051 + *
1052 + * @var string $url The url to clear the cache for.
1053 + */
1054 + do_action( 'optml_clear_cache', $current_url );
1055 + }
1056 + return $this->response( 'Optimization data stored successfully' );
1057 + }
1058 +
1059 + /**
1060 + * Method to register above fold data routes.
1061 + *
1062 + * @return void
1063 + */
1064 + public function register_optimization_routes() {
1065 + foreach ( self::$rest_routes['optimization_routes'] as $route => $details ) {
1066 + $this->register_route( $route, $details[0], $details['args'], $details['permission_callback'] );
1067 + }
1068 + }
1069 +
1070 + /**
1071 + * Move image.
1072 + *
1073 + * @param WP_REST_Request $request Rest request.
1074 + * @phpstan-param WP_REST_Request<array{id: int, action: string, status?: string}> $request
1075 + *
1076 + * @return WP_REST_Response
1077 + */
1078 + public function move_image( WP_REST_Request $request ) {
1079 + $id = $request->get_param( 'id' );
1080 + $action = $request->get_param( 'action' );
1081 +
1082 + if ( $request->get_param( 'status' ) === 'start' ) {
1083 + try {
1084 + Optml_Media_Offload::instance()->move_single_image( $action === 'offload_image' ? 'offload_images' : 'rollback_images', $id );
1085 + } catch ( Exception $e ) {
1086 + return $this->response( strip_tags( $e->getMessage() ), 'error' );
1087 + }
1088 + }
1089 +
1090 + $meta = wp_get_attachment_metadata( $id );
1091 + $file = $meta['file'];
1092 + $result = 'not_moved';
1093 + if ( $action === 'offload_image' ) {
1094 + $result = Optml_Media_Offload::is_uploaded_image( $file ) ? 'moved' : 'not_moved';
1095 + }
1096 +
1097 + if ( $action === 'rollback_image' ) {
1098 + $result = ! Optml_Media_Offload::is_uploaded_image( $file ) ? 'moved' : 'not_moved';
1099 + }
1100 +
1101 + return $this->response( [ $id,$action ], $result );
1102 + }
1103 +
1104 + /**
1105 + * Check if user can move image to/from cloud.
1106 + *
1107 + * @param WP_REST_Request $request Rest request.
1108 + * @phpstan-param WP_REST_Request<array{id: int, action: string}> $request Rest request.
1109 + * @return bool True if user can move image, false otherwise.
1110 + */
1111 + public static function can_move_image( WP_REST_Request $request ) {
1112 +
1113 + if ( ! current_user_can( 'upload_files' ) ) {
1114 + return false;
1115 + }
1116 +
1117 + $id = $request->get_param( 'id' );
1118 +
1119 + if (
1120 + get_post_type( $id ) !== 'attachment' ||
1121 + ! current_user_can( 'edit_post', $id )
1122 + ) {
1123 + return false;
1124 + }
1125 +
1126 + return true;
691 1127 }
692 1128 }