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.15 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 All 136 releases
← All changes | inc/rest.php +444 -198 3.12.0 → 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,19 +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 ) ) {
274 - if ( $data->get_error_code() === 'domain_not_accessible' ) {
275 - return $this->response( $data->get_error_message(), 400 );
276 - }
378 +
277 379 $extra = '';
278 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 + }
279 384 /**
280 385 * Error from api.
281 386 *
282 387 * @var WP_Error $data Error object.
283 388 */
284 - $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() );
285 390 }
286 391 return $this->response( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra, 400 );
287 392 }
288 393 $settings = new Optml_Settings();
@@ -301,8 +406,9 @@
301 406 /**
302 407 * Select application.
303 408 *
304 409 * @param WP_REST_Request $request Rest request.
410 + * @phpstan-param WP_REST_Request<array{api_key: string, application?: string}> $request
305 411 *
306 412 * @return WP_REST_Response
307 413 */
308 414 public function select_application( WP_REST_Request $request ) {
@@ -317,9 +423,9 @@
317 423 * Error from api.
318 424 *
319 425 * @var WP_Error $data Error object.
320 426 */
321 - $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() );
322 428 }
323 429 wp_send_json_error( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra );
324 430 }
325 431 $settings = new Optml_Settings();
@@ -329,9 +435,10 @@
329 435
330 436 /**
331 437 * Wrapper for api response.
332 438 *
333 - * @param mixed $data data from api.
439 + * @param mixed $data data from api.
440 + * @param string|int $code Response code.
334 441 *
335 442 * @return WP_REST_Response
336 443 */
337 444 private function response( $data, $code = 'success' ) {
@@ -341,8 +448,9 @@
341 448 /**
342 449 * Connect to optimole service.
343 450 *
344 451 * @param WP_REST_Request $request connect rest request.
452 + * @phpstan-param WP_REST_Request<array{email: string, auto_connect?: string}> $request
345 453 *
346 454 * @return WP_Error|WP_REST_Response
347 455 */
348 456 public function register_service( WP_REST_Request $request ) {
@@ -366,12 +474,13 @@
366 474 ],
367 475 200
368 476 );
369 477 }
478 +
370 479 return new WP_REST_Response(
371 480 [
372 481 'data' => null,
373 - 'message' => __( 'Error creating account.', 'optimole-wp' ),
482 + 'message' => __( 'Error creating account.', 'optimole-wp' ) . ' ' . $user->get_error_message(),
374 483 'code' => 'error',
375 484 ],
376 485 200
377 486 );
@@ -391,9 +500,9 @@
391 500 if ( $user === 'site_exists' ) {
392 501 return new WP_REST_Response(
393 502 [
394 503 'data' => null,
395 - '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>' ),
396 505 'code' => 'site_exists',
397 506 ],
398 507 200
399 508 );
@@ -418,8 +527,9 @@
418 527 /**
419 528 * Return image samples.
420 529 *
421 530 * @param WP_REST_Request $request Rest request.
531 + * @phpstan-param WP_REST_Request<array{quality?: int, force?: string}> $request
422 532 *
423 533 * @return WP_REST_Response Image urls.
424 534 */
425 535 public function get_sample_rate( WP_REST_Request $request ) {
@@ -466,8 +576,11 @@
466 576
467 577 /**
468 578 * Crawl & upload initial load.
469 579 *
580 + * @param WP_REST_Request $request Rest request.
581 + * @phpstan-param WP_REST_Request<array{offset?: int}> $request
582 + *
470 583 * @return WP_REST_Response If there are more posts left to receive.
471 584 */
472 585 public function upload_onboard_images( WP_REST_Request $request ) {
473 586 $offset = absint( $request->get_param( 'offset' ) );
@@ -523,9 +636,9 @@
523 636
524 637 /**
525 638 * Return sample image data.
526 639 *
527 - * @return array Image data.
640 + * @return array{url: string, width: string|int, height: string|int, id: int} Image data.
528 641 */
529 642 private function fetch_sample_image() {
530 643 $accepted_mimes = [ 'image/jpeg' ];
531 644 $args = [
@@ -573,10 +686,11 @@
573 686
574 687 /**
575 688 * Disconnect from optimole service.
576 689 *
577 - * @SuppressWarnings(PHPMD.UnusedFormalParameter)
578 690 * @param WP_REST_Request $request disconnect rest request.
691 + *
692 + * @return void
579 693 */
580 694 public function disconnect( WP_REST_Request $request ) {
581 695 $settings = new Optml_Settings();
582 696 $settings->reset();
@@ -586,8 +700,9 @@
586 700 /**
587 701 * Get optimized images from API.
588 702 *
589 703 * @param WP_REST_Request $request rest request.
704 + * @phpstan-param WP_REST_Request<array{api_key?: string}> $request
590 705 *
591 706 * @return WP_REST_Response
592 707 */
593 708 public function poll_optimized_images( WP_REST_Request $request ) {
@@ -593,9 +708,9 @@
593 708 public function poll_optimized_images( WP_REST_Request $request ) {
594 709 $api_key = $request->get_param( 'api_key' );
595 710 $request = new Optml_Api();
596 711 $images = $request->get_optimized_images( $api_key );
597 - if ( ! isset( $images['list'] ) || empty( $images['list'] ) ) {
712 + if ( is_wp_error( $images ) || ! is_array( $images ) || empty( $images['list'] ) ) {
598 713 return $this->response( [] );
599 714 }
600 715
601 716 $final_images = array_splice( $images['list'], 0, 10 );
@@ -602,15 +717,15 @@
602 717
603 718 foreach ( $final_images as $index => $value ) {
604 719 $final_images[ $index ]['url'] = Optml_Media_Offload::instance()->get_media_optimized_url(
605 720 $value['url'],
606 - $value['key'],
721 + strtolower( $value['key'] ),
607 722 140,
608 723 140,
609 724 [
610 - 'type' => Optml_Resize::RESIZE_FILL,
725 + 'type' => ResizeTypeProperty::FILL,
611 726 'enlarge' => false,
612 - 'gravity' => Optml_Resize::GRAVITY_CENTER,
727 + 'gravity' => Position::CENTER,
613 728 ]
614 729 );
615 730 unset( $final_images[ $index ]['key'] );
616 731 }
@@ -617,61 +732,10 @@
617 732
618 733 return $this->response( $final_images );
619 734 }
620 735
621 - /**
622 - * Get watermarks from API.
623 - *
624 - * @param WP_REST_Request $request rest request.
625 - *
626 - * @return WP_REST_Response
627 - */
628 - public function poll_watermarks( WP_REST_Request $request ) {
629 - $api_key = $request->get_param( 'api_key' );
630 - $request = new Optml_Api();
631 - $watermarks = $request->get_watermarks( $api_key );
632 - if ( ! isset( $watermarks['watermarks'] ) || empty( $watermarks['watermarks'] ) ) {
633 - return $this->response( [] );
634 - }
635 - $final_images = array_splice( $watermarks['watermarks'], 0, 10 );
636 736
637 - return $this->response( $final_images );
638 - }
639 -
640 737 /**
641 - * Add watermark.
642 - *
643 - * @param WP_REST_Request $request rest request.
644 - *
645 - * @return WP_REST_Response
646 - */
647 - public function add_watermark( WP_REST_Request $request ) {
648 - $file = $request->get_file_params();
649 - $request = new Optml_Api();
650 - $response = $request->add_watermark( $file );
651 - if ( $response === false ) {
652 - return $this->response( __( 'Error uploading image. Please try again.', 'optimole-wp' ), 'error' );
653 - }
654 -
655 - return $this->response( __( 'Watermark image uploaded succesfully ! ', 'optimole-wp' ) );
656 - }
657 -
658 - /**
659 - * Remove watermark.
660 - *
661 - * @param WP_REST_Request $request rest request.
662 - *
663 - * @return WP_REST_Response
664 - */
665 - public function remove_watermark( WP_REST_Request $request ) {
666 - $post_id = $request->get_param( 'postID' );
667 - $api_key = $request->get_param( 'api_key' );
668 - $request = new Optml_Api();
669 -
670 - return $this->response( $request->remove_watermark( $post_id, $api_key ) );
671 - }
672 -
673 - /**
674 738 * Get conflicts from API.
675 739 *
676 740 * @param WP_REST_Request $request rest request.
677 741 *
@@ -692,8 +756,9 @@
692 756 /**
693 757 * Dismiss conflict.
694 758 *
695 759 * @param WP_REST_Request $request rest request.
760 + * @phpstan-param WP_REST_Request<array{conflictID?: string}> $request
696 761 *
697 762 * @return WP_REST_Response
698 763 */
699 764 public function dismiss_conflict( WP_REST_Request $request ) {
@@ -730,8 +795,9 @@
730 795 /**
731 796 * Update options method.
732 797 *
733 798 * @param WP_REST_Request $request option update rest request.
799 + * @phpstan-param WP_REST_Request<array{settings?: array<string, mixed>}> $request
734 800 *
735 801 * @return WP_REST_Response
736 802 */
737 803 public function update_option( WP_REST_Request $request ) {
@@ -747,73 +813,12 @@
747 813 return $this->response( $sanitized );
748 814 }
749 815
750 816 /**
751 - * Update options method.
752 - *
753 - * @param WP_REST_Request $request option update rest request.
754 - *
755 - * @return WP_REST_Response
756 - */
757 - public function check_redirects( WP_REST_Request $request ) {
758 - if ( empty( $request->get_param( 'images' ) ) ) {
759 - return $this->response( __( 'No images available on the current page.' ), 'noImagesFound' );
760 - }
761 - // 'ok' if no issues found, 'log' is there are issues we need to notify, 'deactivated' if the user's account is disabled
762 - $status = 'ok';
763 - $result = '';
764 - foreach ( $request->get_param( 'images' ) as $domain => $value ) {
765 - $args = [
766 - 'method' => 'GET',
767 - 'redirection' => 0,
768 - ];
769 - $processed_images = 0;
770 - if ( isset( $value['src'] ) ) {
771 - $processed_images = count( $value['src'] );
772 - }
773 - if ( isset( $value['ignoredUrls'] ) && $value['ignoredUrls'] > $processed_images ) {
774 - $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>';
775 - $status = 'log';
776 - continue;
777 - }
778 -
779 - if ( $processed_images > 0 ) {
780 - $response = wp_remote_get( $value['src'][ rand( 0, $processed_images - 1 ) ], $args );
781 - if ( ! is_wp_error( $response ) && is_array( $response ) ) {
782 - $headers = $response['headers']; // array of http header lines
783 - $status_code = $response['response']['code'];
784 - if ( $status_code === 301 ) {
785 - $status = 'deactivated';
786 - $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>';
787 - break;
788 - }
789 - if ( $status_code === 302 ) {
790 - if ( isset( $headers['x-redirect-o'] ) ) {
791 - $optimole_code = (int) $headers['x-redirect-o'];
792 - if ( $optimole_code === 1 ) {
793 - $status = 'log';
794 - $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>';
795 - }
796 - if ( $optimole_code === 4 ) {
797 - $status = 'log';
798 - $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>';
799 - }
800 - }
801 - }
802 - }
803 - }
804 - }
805 - if ( $result === '' ) {
806 - $result = __( 'No issues detected, everything is running smoothly.', 'optimole-wp' );
807 - }
808 -
809 - return $this->response( '<ul>' . $result . '</ul>', $status );
810 - }
811 -
812 - /**
813 817 * Get total number of images.
814 818 *
815 819 * @param WP_REST_Request $request rest request object.
820 + * @phpstan-param WP_REST_Request<array{action?: string, refresh?: bool}> $request
816 821 *
817 822 * @return WP_REST_Response
818 823 */
819 824 public function number_of_images_and_pages( WP_REST_Request $request ) {
@@ -818,9 +823,8 @@
818 823 */
819 824 public function number_of_images_and_pages( WP_REST_Request $request ) {
820 825 $action = 'offload_images';
821 826 $refresh = false;
822 - $images = [];
823 827
824 828 if ( ! empty( $request->get_param( 'action' ) ) ) {
825 829 $action = $request->get_param( 'action' );
826 830 }
@@ -828,13 +832,28 @@
828 832 if ( ! empty( $request->get_param( 'refresh' ) ) ) {
829 833 $refresh = $request->get_param( 'refresh' );
830 834 }
831 835
832 - if ( ! empty( $request->get_param( 'images' ) ) ) {
833 - $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();
834 853 }
835 854
836 - return $this->response( Optml_Media_Offload::get_image_count( $action, $refresh, $images ) );
855 + return $this->response( [ 'success' => $delete_count ] );
837 856 }
838 857
839 858 /**
840 859 * Get conflicts list.
@@ -864,8 +883,9 @@
864 883 /**
865 884 * Insert images request.
866 885 *
867 886 * @param WP_REST_Request $request insert images rest request.
887 + * @phpstan-param WP_REST_Request<array{images?: array<mixed>}> $request
868 888 *
869 889 * @return WP_REST_Response
870 890 */
871 891 public function insert_images( WP_REST_Request $request ) {
@@ -877,6 +897,232 @@
877 897
878 898 $insert = Optml_Main::instance()->dam->insert_attachments( $images );
879 899
880 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;
881 1127 }
882 1128 }