PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / trunk
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization vtrunk
4.2.14 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 All 135 releases
← All changes | inc/rest.php +454 -196 3.11.2 → trunk 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
@@ -23,20 +27,35 @@
23 27
24 28 /**
25 29 * Upload conflicts api.
26 30 *
27 - * @var array upload_conflicts_api.
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 + * }
28 41 */
29 42 public static $rest_routes = [
30 - 'service_routes' => ['update_option' => 'POST', 'request_update' => 'GET', 'check_redirects' => 'POST_PUT_PATCH',
31 - 'connect' => [ 'POST', 'args' => [
32 - 'api_key' => [
33 - 'type' => 'string',
34 - 'required' => true,
35 - ],
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,
36 52 ],
37 53 ],
38 - 'select_application' => [ 'POST', 'args' => [
54 + ],
55 + 'select_application' => [
56 + 'POST',
57 + 'args' => [
39 58 'api_key' => [
40 59 'type' => 'string',
41 60 'required' => true,
42 61 ],
@@ -43,17 +62,18 @@
43 62 'application' => [
44 63 'type' => 'string',
45 64 'required' => true,
46 65 ],
66 + ],
47 67 ],
48 - ],
49 - 'register_service' => [ 'POST', 'args' => [
68 + 'register_service' => [
69 + 'POST',
70 + 'args' => [
50 71 'email' => [
51 72 'type' => 'string',
52 73 'required' => true,
53 74 ],
54 75 ],
55 -
56 76 ],
57 77 'disconnect' => 'GET',
58 78 ],
59 79 'image_routes' => [
@@ -58,9 +78,11 @@
58 78 ],
59 79 'image_routes' => [
60 80 'poll_optimized_images' => 'GET',
61 81 'get_sample_rate' => 'POST',
62 - 'upload_onboard_images' => [ 'POST', 'args' => [
82 + 'upload_onboard_images' => [
83 + 'POST',
84 + 'args' => [
63 85 'offset' => [
64 86 'type' => 'number',
65 87 'required' => false,
66 88 'default' => 0,
@@ -69,15 +91,25 @@
69 91 ],
70 92 ],
71 93 'media_cloud_routes' => [
72 94 'number_of_images_and_pages' => 'POST',
95 + 'clear_offload_errors' => 'GET',
73 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 + ],
74 111 ],
75 - 'watermark_routes' => [
76 - 'poll_watermarks' => 'GET',
77 - 'add_watermark' => 'POST',
78 - 'remove_watermark' => 'POST',
79 - ],
80 112 'conflict_routes' => [
81 113 'poll_conflicts' => 'GET',
82 114 'dismiss_conflict' => 'POST',
83 115 ],
@@ -95,12 +127,45 @@
95 127 ],
96 128 'permission_callback' => 'upload_files',
97 129 ],
98 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 + ],
99 162 ];
100 163
101 164 /**
102 165 * Optml_Rest constructor.
166 + *
167 + * @return void
103 168 */
104 169 public function __construct() {
105 170 $this->namespace = OPTML_NAMESPACE . '/v1';
106 171
@@ -107,50 +172,71 @@
107 172 add_action( 'rest_api_init', [ $this, 'register' ] );
108 173 }
109 174
110 175 /**
111 - * Method to register a specific rest route.
176 + * Method to register a specific REST route.
112 177 *
113 - * @param string $route The route name.
114 - * @param string $method The route access method GET, POST, POST_PUT_PATCH.
115 - * @param array $args Optional argument to include required args.
116 - * @param string $permission_callback Optional permission callback.
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
117 188 */
118 - private function reqister_route( $route, $method = 'GET', $args = [], $permission_callback = 'manage_options' ) {
119 - $wp_method_constant = false;
120 - if ( $method === 'GET' ) {
121 - $wp_method_constant = \WP_REST_Server::READABLE;
189 + private function register_route( $route, $method = 'GET', $args = [], $permission_callback = 'manage_options' ) {
190 + if ( empty( $route ) ) {
191 + return;
122 192 }
123 - if ( $method === 'POST' ) {
124 - $wp_method_constant = \WP_REST_Server::CREATABLE;
193 +
194 + $method_map = [
195 + 'GET' => \WP_REST_Server::READABLE,
196 + 'POST' => \WP_REST_Server::CREATABLE,
197 + 'POST_PUT_PATCH' => \WP_REST_Server::EDITABLE,
198 + ];
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;
125 207 }
126 - if ( $method === 'POST_PUT_PATCH' ) {
127 - $wp_method_constant = \WP_REST_Server::EDITABLE;
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;
128 226 }
129 - if ( $wp_method_constant !== false ) {
130 - $params = [
131 - 'methods' => $wp_method_constant,
132 - 'permission_callback' => function () use ( $permission_callback ) {
133 - return current_user_can( $permission_callback );
134 - },
135 - 'callback' => [ $this, $route ],
136 - ];
137 - if ( ! empty( $args ) ) {
138 - $params['args'] = $args;
139 - }
140 - register_rest_route(
141 - $this->namespace,
142 - '/' . $route,
143 - [
144 - $params,
145 - ]
146 - );
147 - }
148 227
228 + register_rest_route(
229 + $this->namespace,
230 + '/' . $route,
231 + [ $params ]
232 + );
149 233 }
150 234
151 235 /**
152 236 * Register rest routes.
237 + *
238 + * @return void
153 239 */
154 240 public function register() {
155 241
156 242 $this->register_service_routes();
@@ -155,24 +241,27 @@
155 241
156 242 $this->register_service_routes();
157 243
158 244 $this->register_image_routes();
159 - $this->register_watermark_routes();
160 245 $this->register_conflict_routes();
161 246 $this->register_cache_routes();
162 247 $this->register_media_offload_routes();
163 248 $this->register_dam_routes();
249 + $this->register_notification_routes();
250 + $this->register_optimization_routes();
164 251 }
165 252
166 253 /**
167 254 * Method to register service specific routes.
255 + *
256 + * @return void
168 257 */
169 258 public function register_service_routes() {
170 259 foreach ( self::$rest_routes['service_routes'] as $route => $details ) {
171 260 if ( is_array( $details ) ) {
172 - $this->reqister_route( $route, $details[0], $details['args'] );
261 + $this->register_route( $route, $details[0], $details['args'] );
173 262 } else {
174 - $this->reqister_route( $route, $details );
263 + $this->register_route( $route, $details );
175 264 }
176 265 }
177 266 }
178 267
@@ -177,52 +266,54 @@
177 266 }
178 267
179 268 /**
180 269 * Method to register image specific routes.
270 + *
271 + * @return void
181 272 */
182 273 public function register_image_routes() {
183 274 foreach ( self::$rest_routes['image_routes'] as $route => $details ) {
184 275 if ( is_array( $details ) ) {
185 - $this->reqister_route( $route, $details[0], $details['args'] );
276 + $this->register_route( $route, $details[0], $details['args'] );
186 277 } else {
187 - $this->reqister_route( $route, $details );
278 + $this->register_route( $route, $details );
188 279 }
189 280 }
190 -
191 281 }
192 282 /**
193 283 * Method to register media offload specific routes.
284 + *
285 + * @return void
194 286 */
195 287 public function register_media_offload_routes() {
196 288 foreach ( self::$rest_routes['media_cloud_routes'] as $route => $details ) {
197 - $this->reqister_route( $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 );
198 293 }
199 294 }
200 295
201 - /**
202 - * Method to register watermark specific routes.
203 - */
204 - public function register_watermark_routes() {
205 - foreach ( self::$rest_routes['watermark_routes'] as $route => $details ) {
206 - $this->reqister_route( $route, $details );
207 - }
208 - }
209 296
210 297 /**
211 298 * Method to register conflicts specific routes.
299 + *
300 + * @return void
212 301 */
213 302 public function register_conflict_routes() {
214 303 foreach ( self::$rest_routes['conflict_routes'] as $route => $details ) {
215 - $this->reqister_route( $route, $details );
304 + $this->register_route( $route, $details );
216 305 }
217 306 }
218 307
219 308 /**
220 309 * Method to register cache specific routes.
310 + *
311 + * @return void
221 312 */
222 313 public function register_cache_routes() {
223 314 foreach ( self::$rest_routes['cache_routes'] as $route => $details ) {
224 - $this->reqister_route( $route, $details );
315 + $this->register_route( $route, $details );
225 316 }
226 317 }
227 318
228 319 /**
@@ -233,16 +324,28 @@
233 324 public function register_dam_routes() {
234 325 foreach ( self::$rest_routes['dam_routes'] as $route => $details ) {
235 326 $permission = isset( $details['permission_callback'] ) ? $details['permission_callback'] : 'manage_options';
236 327 $args = isset( $details['args'] ) ? $details['args'] : [];
237 - $this->reqister_route( $route, $details[0], $args, $permission );
328 + $this->register_route( $route, $details[0], $args, $permission );
238 329 }
239 330 }
240 331
241 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 + /**
242 344 * Clear Cache request.
243 345 *
244 346 * @param WP_REST_Request $request clear cache rest request.
347 + * @phpstan-param WP_REST_Request<array{type?: string}> $request
245 348 *
246 349 * @return WP_Error|WP_REST_Response
247 350 */
248 351 public function clear_cache_request( WP_REST_Request $request ) {
@@ -260,8 +363,9 @@
260 363 /**
261 364 * Connect to optimole service.
262 365 *
263 366 * @param WP_REST_Request $request connect rest request.
367 + * @phpstan-param WP_REST_Request<array{api_key: string, application?: string}> $request
264 368 *
265 369 * @return WP_Error|WP_REST_Response
266 370 */
267 371 public function connect( WP_REST_Request $request ) {
@@ -270,16 +374,20 @@
270 374 $request = new Optml_Api();
271 375 $data = $request->connect( $api_key );
272 376
273 377 if ( $data === false || is_wp_error( $data ) ) {
378 +
274 379 $extra = '';
275 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 + }
276 384 /**
277 385 * Error from api.
278 386 *
279 387 * @var WP_Error $data Error object.
280 388 */
281 - $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() );
282 390 }
283 391 return $this->response( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra, 400 );
284 392 }
285 393 $settings = new Optml_Settings();
@@ -298,8 +406,9 @@
298 406 /**
299 407 * Select application.
300 408 *
301 409 * @param WP_REST_Request $request Rest request.
410 + * @phpstan-param WP_REST_Request<array{api_key: string, application?: string}> $request
302 411 *
303 412 * @return WP_REST_Response
304 413 */
305 414 public function select_application( WP_REST_Request $request ) {
@@ -314,9 +423,9 @@
314 423 * Error from api.
315 424 *
316 425 * @var WP_Error $data Error object.
317 426 */
318 - $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() );
319 428 }
320 429 wp_send_json_error( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra );
321 430 }
322 431 $settings = new Optml_Settings();
@@ -326,9 +435,10 @@
326 435
327 436 /**
328 437 * Wrapper for api response.
329 438 *
330 - * @param mixed $data data from api.
439 + * @param mixed $data data from api.
440 + * @param string|int $code Response code.
331 441 *
332 442 * @return WP_REST_Response
333 443 */
334 444 private function response( $data, $code = 'success' ) {
@@ -338,8 +448,9 @@
338 448 /**
339 449 * Connect to optimole service.
340 450 *
341 451 * @param WP_REST_Request $request connect rest request.
452 + * @phpstan-param WP_REST_Request<array{email: string, auto_connect?: string}> $request
342 453 *
343 454 * @return WP_Error|WP_REST_Response
344 455 */
345 456 public function register_service( WP_REST_Request $request ) {
@@ -352,14 +463,24 @@
352 463
353 464 if ( ! empty( $auto_connect ) && $auto_connect === 'true' ) {
354 465 delete_option( Optml_Settings::OPTML_USER_EMAIL );
355 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 + }
356 478
357 - if ( $user === false || is_wp_error( $user ) ) {
358 479 return new WP_REST_Response(
359 480 [
360 481 'data' => null,
361 - 'message' => __( 'Error creating account.', 'optimole-wp' ),
482 + 'message' => __( 'Error creating account.', 'optimole-wp' ) . ' ' . $user->get_error_message(),
362 483 'code' => 'error',
363 484 ],
364 485 200
365 486 );
@@ -379,9 +500,9 @@
379 500 if ( $user === 'site_exists' ) {
380 501 return new WP_REST_Response(
381 502 [
382 503 'data' => null,
383 - 'message' => sprintf( __( 'Error: This site has been previously registered. You can login to your account from %1$shere%2$s ', 'optimole-wp' ), '<a href="https://dashboard.optimole.com/login" target="_blank"> ', '</a>' ),
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>' ),
384 505 'code' => 'site_exists',
385 506 ],
386 507 200
387 508 );
@@ -406,8 +527,9 @@
406 527 /**
407 528 * Return image samples.
408 529 *
409 530 * @param WP_REST_Request $request Rest request.
531 + * @phpstan-param WP_REST_Request<array{quality?: int, force?: string}> $request
410 532 *
411 533 * @return WP_REST_Response Image urls.
412 534 */
413 535 public function get_sample_rate( WP_REST_Request $request ) {
@@ -454,8 +576,11 @@
454 576
455 577 /**
456 578 * Crawl & upload initial load.
457 579 *
580 + * @param WP_REST_Request $request Rest request.
581 + * @phpstan-param WP_REST_Request<array{offset?: int}> $request
582 + *
458 583 * @return WP_REST_Response If there are more posts left to receive.
459 584 */
460 585 public function upload_onboard_images( WP_REST_Request $request ) {
461 586 $offset = absint( $request->get_param( 'offset' ) );
@@ -511,9 +636,9 @@
511 636
512 637 /**
513 638 * Return sample image data.
514 639 *
515 - * @return array Image data.
640 + * @return array{url: string, width: string|int, height: string|int, id: int} Image data.
516 641 */
517 642 private function fetch_sample_image() {
518 643 $accepted_mimes = [ 'image/jpeg' ];
519 644 $args = [
@@ -561,10 +686,11 @@
561 686
562 687 /**
563 688 * Disconnect from optimole service.
564 689 *
565 - * @SuppressWarnings(PHPMD.UnusedFormalParameter)
566 690 * @param WP_REST_Request $request disconnect rest request.
691 + *
692 + * @return void
567 693 */
568 694 public function disconnect( WP_REST_Request $request ) {
569 695 $settings = new Optml_Settings();
570 696 $settings->reset();
@@ -574,8 +700,9 @@
574 700 /**
575 701 * Get optimized images from API.
576 702 *
577 703 * @param WP_REST_Request $request rest request.
704 + * @phpstan-param WP_REST_Request<array{api_key?: string}> $request
578 705 *
579 706 * @return WP_REST_Response
580 707 */
581 708 public function poll_optimized_images( WP_REST_Request $request ) {
@@ -581,9 +708,9 @@
581 708 public function poll_optimized_images( WP_REST_Request $request ) {
582 709 $api_key = $request->get_param( 'api_key' );
583 710 $request = new Optml_Api();
584 711 $images = $request->get_optimized_images( $api_key );
585 - if ( ! isset( $images['list'] ) || empty( $images['list'] ) ) {
712 + if ( is_wp_error( $images ) || ! is_array( $images ) || empty( $images['list'] ) ) {
586 713 return $this->response( [] );
587 714 }
588 715
589 716 $final_images = array_splice( $images['list'], 0, 10 );
@@ -590,15 +717,15 @@
590 717
591 718 foreach ( $final_images as $index => $value ) {
592 719 $final_images[ $index ]['url'] = Optml_Media_Offload::instance()->get_media_optimized_url(
593 720 $value['url'],
594 - $value['key'],
721 + strtolower( $value['key'] ),
595 722 140,
596 723 140,
597 724 [
598 - 'type' => Optml_Resize::RESIZE_FILL,
725 + 'type' => ResizeTypeProperty::FILL,
599 726 'enlarge' => false,
600 - 'gravity' => Optml_Resize::GRAVITY_CENTER,
727 + 'gravity' => Position::CENTER,
601 728 ]
602 729 );
603 730 unset( $final_images[ $index ]['key'] );
604 731 }
@@ -605,61 +732,10 @@
605 732
606 733 return $this->response( $final_images );
607 734 }
608 735
609 - /**
610 - * Get watermarks from API.
611 - *
612 - * @param WP_REST_Request $request rest request.
613 - *
614 - * @return WP_REST_Response
615 - */
616 - public function poll_watermarks( WP_REST_Request $request ) {
617 - $api_key = $request->get_param( 'api_key' );
618 - $request = new Optml_Api();
619 - $watermarks = $request->get_watermarks( $api_key );
620 - if ( ! isset( $watermarks['watermarks'] ) || empty( $watermarks['watermarks'] ) ) {
621 - return $this->response( [] );
622 - }
623 - $final_images = array_splice( $watermarks['watermarks'], 0, 10 );
624 736
625 - return $this->response( $final_images );
626 - }
627 -
628 737 /**
629 - * Add watermark.
630 - *
631 - * @param WP_REST_Request $request rest request.
632 - *
633 - * @return WP_REST_Response
634 - */
635 - public function add_watermark( WP_REST_Request $request ) {
636 - $file = $request->get_file_params();
637 - $request = new Optml_Api();
638 - $response = $request->add_watermark( $file );
639 - if ( $response === false ) {
640 - return $this->response( __( 'Error uploading image. Please try again.', 'optimole-wp' ), 'error' );
641 - }
642 -
643 - return $this->response( __( 'Watermark image uploaded succesfully ! ', 'optimole-wp' ) );
644 - }
645 -
646 - /**
647 - * Remove watermark.
648 - *
649 - * @param WP_REST_Request $request rest request.
650 - *
651 - * @return WP_REST_Response
652 - */
653 - public function remove_watermark( WP_REST_Request $request ) {
654 - $post_id = $request->get_param( 'postID' );
655 - $api_key = $request->get_param( 'api_key' );
656 - $request = new Optml_Api();
657 -
658 - return $this->response( $request->remove_watermark( $post_id, $api_key ) );
659 - }
660 -
661 - /**
662 738 * Get conflicts from API.
663 739 *
664 740 * @param WP_REST_Request $request rest request.
665 741 *
@@ -680,8 +756,9 @@
680 756 /**
681 757 * Dismiss conflict.
682 758 *
683 759 * @param WP_REST_Request $request rest request.
760 + * @phpstan-param WP_REST_Request<array{conflictID?: string}> $request
684 761 *
685 762 * @return WP_REST_Response
686 763 */
687 764 public function dismiss_conflict( WP_REST_Request $request ) {
@@ -718,8 +795,9 @@
718 795 /**
719 796 * Update options method.
720 797 *
721 798 * @param WP_REST_Request $request option update rest request.
799 + * @phpstan-param WP_REST_Request<array{settings?: array<string, mixed>}> $request
722 800 *
723 801 * @return WP_REST_Response
724 802 */
725 803 public function update_option( WP_REST_Request $request ) {
@@ -735,73 +813,12 @@
735 813 return $this->response( $sanitized );
736 814 }
737 815
738 816 /**
739 - * Update options method.
740 - *
741 - * @param WP_REST_Request $request option update rest request.
742 - *
743 - * @return WP_REST_Response
744 - */
745 - public function check_redirects( WP_REST_Request $request ) {
746 - if ( empty( $request->get_param( 'images' ) ) ) {
747 - return $this->response( __( 'No images available on the current page.' ), 'noImagesFound' );
748 - }
749 - // 'ok' if no issues found, 'log' is there are issues we need to notify, 'deactivated' if the user's account is disabled
750 - $status = 'ok';
751 - $result = '';
752 - foreach ( $request->get_param( 'images' ) as $domain => $value ) {
753 - $args = [
754 - 'method' => 'GET',
755 - 'redirection' => 0,
756 - ];
757 - $processed_images = 0;
758 - if ( isset( $value['src'] ) ) {
759 - $processed_images = count( $value['src'] );
760 - }
761 - if ( isset( $value['ignoredUrls'] ) && $value['ignoredUrls'] > $processed_images ) {
762 - $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>';
763 - $status = 'log';
764 - continue;
765 - }
766 -
767 - if ( $processed_images > 0 ) {
768 - $response = wp_remote_get( $value['src'][ rand( 0, $processed_images - 1 ) ], $args );
769 - if ( ! is_wp_error( $response ) && is_array( $response ) ) {
770 - $headers = $response['headers']; // array of http header lines
771 - $status_code = $response['response']['code'];
772 - if ( $status_code === 301 ) {
773 - $status = 'deactivated';
774 - $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>';
775 - break;
776 - }
777 - if ( $status_code === 302 ) {
778 - if ( isset( $headers['x-redirect-o'] ) ) {
779 - $optimole_code = (int) $headers['x-redirect-o'];
780 - if ( $optimole_code === 1 ) {
781 - $status = 'log';
782 - $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>';
783 - }
784 - if ( $optimole_code === 4 ) {
785 - $status = 'log';
786 - $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>';
787 - }
788 - }
789 - }
790 - }
791 - }
792 - }
793 - if ( $result === '' ) {
794 - $result = __( 'No issues detected, everything is running smoothly.', 'optimole-wp' );
795 - }
796 -
797 - return $this->response( '<ul>' . $result . '</ul>', $status );
798 - }
799 -
800 - /**
801 817 * Get total number of images.
802 818 *
803 819 * @param WP_REST_Request $request rest request object.
820 + * @phpstan-param WP_REST_Request<array{action?: string, refresh?: bool}> $request
804 821 *
805 822 * @return WP_REST_Response
806 823 */
807 824 public function number_of_images_and_pages( WP_REST_Request $request ) {
@@ -806,9 +823,8 @@
806 823 */
807 824 public function number_of_images_and_pages( WP_REST_Request $request ) {
808 825 $action = 'offload_images';
809 826 $refresh = false;
810 - $images = [];
811 827
812 828 if ( ! empty( $request->get_param( 'action' ) ) ) {
813 829 $action = $request->get_param( 'action' );
814 830 }
@@ -816,13 +832,28 @@
816 832 if ( ! empty( $request->get_param( 'refresh' ) ) ) {
817 833 $refresh = $request->get_param( 'refresh' );
818 834 }
819 835
820 - if ( ! empty( $request->get_param( 'images' ) ) ) {
821 - $images = $request->get_param( 'images' );
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();
822 853 }
823 854
824 - return $this->response( Optml_Media_Offload::get_image_count( $action, $refresh, $images ) );
855 + return $this->response( [ 'success' => $delete_count ] );
825 856 }
826 857
827 858 /**
828 859 * Get conflicts list.
@@ -852,8 +883,9 @@
852 883 /**
853 884 * Insert images request.
854 885 *
855 886 * @param WP_REST_Request $request insert images rest request.
887 + * @phpstan-param WP_REST_Request<array{images?: array<mixed>}> $request
856 888 *
857 889 * @return WP_REST_Response
858 890 */
859 891 public function insert_images( WP_REST_Request $request ) {
@@ -865,6 +897,232 @@
865 897
866 898 $insert = Optml_Main::instance()->dam->insert_attachments( $images );
867 899
868 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' );
981 + }
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;
996 + }
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;
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'] ) ];
1016 + }
1017 + }
1018 + }
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 + }
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 + }
1041 +
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;
869 1127 }
870 1128 }