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 +734 -386 3.0.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
@@ -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,341 +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';
171 +
28 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 - [
42 - [
43 - 'methods' => \WP_REST_Server::CREATABLE,
44 - 'permission_callback' => function () {
45 - return current_user_can( 'manage_options' );
46 - },
47 - 'callback' => [ $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 - [
56 - [
57 - 'methods' => \WP_REST_Server::READABLE,
58 - 'permission_callback' => function () {
59 - return current_user_can( 'manage_options' );
60 - },
61 - 'callback' => [ $this, 'request_update' ],
62 - ],
63 - ]
64 - );
65 - register_rest_route(
66 - $this->namespace,
67 - '/check_redirects',
68 - [
69 - [
70 - 'methods' => \WP_REST_Server::EDITABLE,
71 - 'permission_callback' => function () {
72 - return current_user_can( 'manage_options' );
73 - },
74 - 'callback' => [ $this, 'check_redirects' ],
75 - ],
76 - ]
77 - );
235 + /**
236 + * Register rest routes.
237 + *
238 + * @return void
239 + */
240 + public function register() {
78 241
242 + $this->register_service_routes();
243 +
79 244 $this->register_image_routes();
80 - $this->register_watermark_routes();
81 245 $this->register_conflict_routes();
82 246 $this->register_cache_routes();
83 247 $this->register_media_offload_routes();
248 + $this->register_dam_routes();
249 + $this->register_notification_routes();
250 + $this->register_optimization_routes();
84 251 }
85 252
86 253 /**
87 254 * Method to register service specific routes.
255 + *
256 + * @return void
88 257 */
89 258 public function register_service_routes() {
90 - register_rest_route(
91 - $this->namespace,
92 - '/connect',
93 - [
94 - [
95 - 'methods' => \WP_REST_Server::CREATABLE,
96 - 'permission_callback' => function () {
97 - return current_user_can( 'manage_options' );
98 - },
99 - 'callback' => [ $this, 'connect' ],
100 - 'args' => [
101 - 'api_key' => [
102 - 'type' => 'string',
103 - 'required' => true,
104 - ],
105 - ],
106 - ],
107 - ]
108 - );
109 - register_rest_route(
110 - $this->namespace,
111 - '/register',
112 - [
113 - [
114 - 'methods' => \WP_REST_Server::CREATABLE,
115 - 'permission_callback' => function () {
116 - return current_user_can( 'manage_options' );
117 - },
118 - 'callback' => [ $this, 'register_service' ],
119 - 'args' => [
120 - 'email' => [
121 - 'type' => 'string',
122 - 'required' => true,
123 - ],
124 - ],
125 - ],
126 - ]
127 - );
128 - register_rest_route(
129 - $this->namespace,
130 - '/disconnect',
131 - [
132 - [
133 - 'methods' => \WP_REST_Server::READABLE,
134 - 'permission_callback' => function () {
135 - return current_user_can( 'manage_options' );
136 - },
137 - 'callback' => [ $this, 'disconnect' ],
138 - ],
139 - ]
140 - );
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 + }
141 266 }
142 267
143 268 /**
144 269 * Method to register image specific routes.
270 + *
271 + * @return void
145 272 */
146 273 public function register_image_routes() {
147 - register_rest_route(
148 - $this->namespace,
149 - '/poll_optimized_images',
150 - [
151 - [
152 - 'methods' => \WP_REST_Server::READABLE,
153 - 'permission_callback' => function () {
154 - return current_user_can( 'manage_options' );
155 - },
156 - 'callback' => [ $this, 'poll_optimized_images' ],
157 - ],
158 - ]
159 - );
160 - register_rest_route(
161 - $this->namespace,
162 - '/images-sample-rate',
163 - [
164 - [
165 - 'methods' => \WP_REST_Server::CREATABLE,
166 - 'permission_callback' => function () {
167 - return current_user_can( 'manage_options' );
168 - },
169 - 'callback' => [ $this, 'get_sample_rate' ],
170 - ],
171 - ]
172 - );
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 + }
173 281 }
174 282 /**
175 283 * Method to register media offload specific routes.
284 + *
285 + * @return void
176 286 */
177 287 public function register_media_offload_routes() {
178 - register_rest_route(
179 - $this->namespace,
180 - '/offload_images',
181 - [
182 - [
183 - 'methods' => \WP_REST_Server::CREATABLE,
184 - 'permission_callback' => function () {
185 - return current_user_can( 'manage_options' );
186 - },
187 - 'callback' => [ $this, 'offload_images' ],
188 - ],
189 - ]
190 - );
191 - register_rest_route(
192 - $this->namespace,
193 - '/rollback_images',
194 - [
195 - [
196 - 'methods' => \WP_REST_Server::CREATABLE,
197 - 'permission_callback' => function () {
198 - return current_user_can( 'manage_options' );
199 - },
200 - 'callback' => [ $this, 'rollback_images' ],
201 - ],
202 - ]
203 - );
204 - register_rest_route(
205 - $this->namespace,
206 - '/number_of_library_images',
207 - [
208 - [
209 - 'methods' => \WP_REST_Server::CREATABLE,
210 - 'permission_callback' => function () {
211 - return current_user_can( 'manage_options' );
212 - },
213 - 'callback' => [ $this, 'number_of_library_images' ],
214 - ],
215 - ]
216 - );
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 + }
217 294 }
218 295
219 - /**
220 - * Method to register watermark specific routes.
221 - */
222 - public function register_watermark_routes() {
223 - register_rest_route(
224 - $this->namespace,
225 - '/poll_watermarks',
226 - [
227 - [
228 - 'methods' => \WP_REST_Server::READABLE,
229 - 'permission_callback' => function () {
230 - return current_user_can( 'manage_options' );
231 - },
232 - 'callback' => [ $this, 'poll_watermarks' ],
233 - ],
234 - ]
235 - );
236 - register_rest_route(
237 - $this->namespace,
238 - '/add_watermark',
239 - [
240 - [
241 - 'methods' => \WP_REST_Server::CREATABLE,
242 - 'permission_callback' => function () {
243 - return current_user_can( 'manage_options' );
244 - },
245 - 'callback' => [ $this, 'add_watermark' ],
246 - ],
247 - ]
248 - );
249 - register_rest_route(
250 - $this->namespace,
251 - '/remove_watermark',
252 - [
253 - [
254 - 'methods' => \WP_REST_Server::CREATABLE,
255 - 'permission_callback' => function () {
256 - return current_user_can( 'manage_options' );
257 - },
258 - 'callback' => [ $this, 'remove_watermark' ],
259 - ],
260 - ]
261 - );
262 - }
263 296
264 297 /**
265 298 * Method to register conflicts specific routes.
299 + *
300 + * @return void
266 301 */
267 302 public function register_conflict_routes() {
268 - register_rest_route(
269 - $this->namespace,
270 - '/poll_conflicts',
271 - [
272 - [
273 - 'methods' => \WP_REST_Server::READABLE,
274 - 'permission_callback' => function () {
275 - return current_user_can( 'manage_options' );
276 - },
277 - 'callback' => [ $this, 'poll_conflicts' ],
278 - ],
279 - ]
280 - );
281 - register_rest_route(
282 - $this->namespace,
283 - '/dismiss_conflict',
284 - [
285 - [
286 - 'methods' => \WP_REST_Server::CREATABLE,
287 - 'permission_callback' => function () {
288 - return current_user_can( 'manage_options' );
289 - },
290 - 'callback' => [ $this, 'dismiss_conflict' ],
291 - ],
292 - ]
293 - );
303 + foreach ( self::$rest_routes['conflict_routes'] as $route => $details ) {
304 + $this->register_route( $route, $details );
305 + }
294 306 }
295 307
296 308 /**
297 309 * Method to register cache specific routes.
310 + *
311 + * @return void
298 312 */
299 313 public function register_cache_routes() {
300 - register_rest_route(
301 - $this->namespace,
302 - '/clear_cache',
303 - [
304 - [
305 - 'methods' => \WP_REST_Server::CREATABLE,
306 - 'permission_callback' => function () {
307 - return current_user_can( 'manage_options' );
308 - },
309 - 'callback' => [ $this, 'clear_cache_request' ],
310 - ],
311 - ]
312 - );
314 + foreach ( self::$rest_routes['cache_routes'] as $route => $details ) {
315 + $this->register_route( $route, $details );
316 + }
313 317 }
314 318
315 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 + /**
316 344 * Clear Cache request.
317 345 *
318 346 * @param WP_REST_Request $request clear cache rest request.
347 + * @phpstan-param WP_REST_Request<array{type?: string}> $request
319 348 *
320 349 * @return WP_Error|WP_REST_Response
321 350 */
322 351 public function clear_cache_request( WP_REST_Request $request ) {
323 352 $settings = new Optml_Settings();
324 - $token = $settings->get( 'cache_buster' );
325 - $request = new Optml_Api();
326 - $data = $request->get_cache_token( $token );
327 - 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 +
328 379 $extra = '';
329 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 + }
330 384 /**
331 385 * Error from api.
332 386 *
333 387 * @var WP_Error $data Error object.
334 388 */
335 - $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() );
336 390 }
337 - 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 );
338 392 }
393 + $settings = new Optml_Settings();
394 + $settings->update( 'api_key', $api_key );
339 395
340 - set_transient( 'optml_cache_lock', 'yes', 5 * MINUTE_IN_SECONDS );
341 - $settings->update( 'cache_buster', $data['token'] );
396 + if ( isset( $data['extra_visits'] ) ) {
397 + $settings->update_frontend_banner_from_remote( $data['extra_visits'] );
398 + }
342 399
343 - 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 );
344 404 }
345 405
346 406 /**
347 - * Connect to optimole service.
407 + * Select application.
348 408 *
349 - * @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
350 411 *
351 - * @return WP_Error|WP_REST_Response
412 + * @return WP_REST_Response
352 413 */
353 - public function connect( WP_REST_Request $request ) {
414 + public function select_application( WP_REST_Request $request ) {
354 415 $api_key = $request->get_param( 'api_key' );
416 + $application = $request->get_param( 'application' );
355 417 $request = new Optml_Api();
356 - $data = $request->get_user_data( $api_key );
418 + $data = $request->get_user_data( $api_key, $application );
357 419 if ( $data === false || is_wp_error( $data ) ) {
358 420 $extra = '';
359 421 if ( is_wp_error( $data ) ) {
360 422 /**
@@ -361,16 +423,14 @@
361 423 * Error from api.
362 424 *
363 425 * @var WP_Error $data Error object.
364 426 */
365 - $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() );
366 428 }
367 429 wp_send_json_error( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra );
368 430 }
369 431 $settings = new Optml_Settings();
370 432 $settings->update( 'service_data', $data );
371 - $settings->update( 'api_key', $api_key );
372 -
373 433 return $this->response( $data );
374 434 }
375 435
376 436 /**
@@ -375,9 +435,10 @@
375 435
376 436 /**
377 437 * Wrapper for api response.
378 438 *
379 - * @param mixed $data data from api.
439 + * @param mixed $data data from api.
440 + * @param string|int $code Response code.
380 441 *
381 442 * @return WP_REST_Response
382 443 */
383 444 private function response( $data, $code = 'success' ) {
@@ -387,8 +448,9 @@
387 448 /**
388 449 * Connect to optimole service.
389 450 *
390 451 * @param WP_REST_Request $request connect rest request.
452 + * @phpstan-param WP_REST_Request<array{email: string, auto_connect?: string}> $request
391 453 *
392 454 * @return WP_Error|WP_REST_Response
393 455 */
394 456 public function register_service( WP_REST_Request $request ) {
@@ -393,22 +455,74 @@
393 455 */
394 456 public function register_service( WP_REST_Request $request ) {
395 457 $email = $request->get_param( 'email' );
396 458 $api = new Optml_Api();
459 +
397 460 $user = $api->create_account( $email );
398 - 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 +
399 479 return new WP_REST_Response(
400 480 [
401 481 'data' => null,
402 - 'message' => __( 'Error creating account.', 'optimole-wp' ),
482 + 'message' => __( 'Error creating account.', 'optimole-wp' ) . ' ' . $user->get_error_message(),
403 483 'code' => 'error',
404 484 ],
405 485 200
406 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 + );
407 497
408 498 }
409 499
410 - 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 );
411 525 }
412 526
413 527 /**
414 528 * Return image samples.
@@ -413,8 +527,9 @@
413 527 /**
414 528 * Return image samples.
415 529 *
416 530 * @param WP_REST_Request $request Rest request.
531 + * @phpstan-param WP_REST_Request<array{quality?: int, force?: string}> $request
417 532 *
418 533 * @return WP_REST_Response Image urls.
419 534 */
420 535 public function get_sample_rate( WP_REST_Request $request ) {
@@ -459,11 +574,71 @@
459 574 return $this->response( $image );
460 575 }
461 576
462 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 + /**
463 638 * Return sample image data.
464 639 *
465 - * @return array Image data.
640 + * @return array{url: string, width: string|int, height: string|int, id: int} Image data.
466 641 */
467 642 private function fetch_sample_image() {
468 643 $accepted_mimes = [ 'image/jpeg' ];
469 644 $args = [
@@ -511,10 +686,11 @@
511 686
512 687 /**
513 688 * Disconnect from optimole service.
514 689 *
515 - * @SuppressWarnings(PHPMD.UnusedFormalParameter)
516 690 * @param WP_REST_Request $request disconnect rest request.
691 + *
692 + * @return void
517 693 */
518 694 public function disconnect( WP_REST_Request $request ) {
519 695 $settings = new Optml_Settings();
520 696 $settings->reset();
@@ -524,8 +700,9 @@
524 700 /**
525 701 * Get optimized images from API.
526 702 *
527 703 * @param WP_REST_Request $request rest request.
704 + * @phpstan-param WP_REST_Request<array{api_key?: string}> $request
528 705 *
529 706 * @return WP_REST_Response
530 707 */
531 708 public function poll_optimized_images( WP_REST_Request $request ) {
@@ -531,17 +708,26 @@
531 708 public function poll_optimized_images( WP_REST_Request $request ) {
532 709 $api_key = $request->get_param( 'api_key' );
533 710 $request = new Optml_Api();
534 711 $images = $request->get_optimized_images( $api_key );
535 - if ( ! isset( $images['list'] ) || empty( $images['list'] ) ) {
712 + if ( is_wp_error( $images ) || ! is_array( $images ) || empty( $images['list'] ) ) {
536 713 return $this->response( [] );
537 714 }
538 715
539 716 $final_images = array_splice( $images['list'], 0, 10 );
540 - $media = new Optml_Media_Offload();
541 717
542 718 foreach ( $final_images as $index => $value ) {
543 - $final_images[ $index ]['url'] = $media->get_media_optimized_url( $value['url'], $value['key'] );
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 + );
544 730 unset( $final_images[ $index ]['key'] );
545 731 }
546 732
547 733 return $this->response( $final_images );
@@ -546,61 +732,10 @@
546 732
547 733 return $this->response( $final_images );
548 734 }
549 735
550 - /**
551 - * Get watermarks from API.
552 - *
553 - * @param WP_REST_Request $request rest request.
554 - *
555 - * @return WP_REST_Response
556 - */
557 - public function poll_watermarks( WP_REST_Request $request ) {
558 - $api_key = $request->get_param( 'api_key' );
559 - $request = new Optml_Api();
560 - $watermarks = $request->get_watermarks( $api_key );
561 - if ( ! isset( $watermarks['watermarks'] ) || empty( $watermarks['watermarks'] ) ) {
562 - return $this->response( [] );
563 - }
564 - $final_images = array_splice( $watermarks['watermarks'], 0, 10 );
565 736
566 - return $this->response( $final_images );
567 - }
568 -
569 737 /**
570 - * Add watermark.
571 - *
572 - * @param WP_REST_Request $request rest request.
573 - *
574 - * @return WP_REST_Response
575 - */
576 - public function add_watermark( WP_REST_Request $request ) {
577 - $file = $request->get_file_params();
578 - $request = new Optml_Api();
579 - $response = $request->add_watermark( $file );
580 - if ( $response === false ) {
581 - return $this->response( __( 'Error uploading image. Please try again.', 'optimole-wp' ), 'error' );
582 - }
583 -
584 - return $this->response( __( 'Watermark image uploaded succesfully ! ', 'optimole-wp' ) );
585 - }
586 -
587 - /**
588 - * Remove watermark.
589 - *
590 - * @param WP_REST_Request $request rest request.
591 - *
592 - * @return WP_REST_Response
593 - */
594 - public function remove_watermark( WP_REST_Request $request ) {
595 - $post_id = $request->get_param( 'postID' );
596 - $api_key = $request->get_param( 'api_key' );
597 - $request = new Optml_Api();
598 -
599 - return $this->response( $request->remove_watermark( $post_id, $api_key ) );
600 - }
601 -
602 - /**
603 738 * Get conflicts from API.
604 739 *
605 740 * @param WP_REST_Request $request rest request.
606 741 *
@@ -621,8 +756,9 @@
621 756 /**
622 757 * Dismiss conflict.
623 758 *
624 759 * @param WP_REST_Request $request rest request.
760 + * @phpstan-param WP_REST_Request<array{conflictID?: string}> $request
625 761 *
626 762 * @return WP_REST_Response
627 763 */
628 764 public function dismiss_conflict( WP_REST_Request $request ) {
@@ -648,10 +784,13 @@
648 784 */
649 785 public function request_update( WP_REST_Request $request ) {
650 786 do_action( 'optml_daily_sync' );
651 787 $settings = new Optml_Settings();
652 -
653 - 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 );
654 793 }
655 794
656 795 /**
657 796 * Update options method.
@@ -656,8 +795,9 @@
656 795 /**
657 796 * Update options method.
658 797 *
659 798 * @param WP_REST_Request $request option update rest request.
799 + * @phpstan-param WP_REST_Request<array{settings?: array<string, mixed>}> $request
660 800 *
661 801 * @return WP_REST_Response
662 802 */
663 803 public function update_option( WP_REST_Request $request ) {
@@ -673,108 +813,316 @@
673 813 return $this->response( $sanitized );
674 814 }
675 815
676 816 /**
677 - * Update options method.
817 + * Get total number of images.
678 818 *
679 - * @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
680 821 *
681 822 * @return WP_REST_Response
682 823 */
683 - public function check_redirects( WP_REST_Request $request ) {
684 - if ( empty( $request->get_param( 'images' ) ) ) {
685 - 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' );
686 830 }
687 - // 'ok' if no issues found, 'log' is there are issues we need to notify, 'deactivated' if the user's account is disabled
688 - $status = 'ok';
689 - $result = '';
690 - foreach ( $request->get_param( 'images' ) as $domain => $value ) {
691 - $args = [
692 - 'method' => 'GET',
693 - 'redirection' => 0,
694 - ];
695 - $processed_images = 0;
696 - if ( isset( $value['src'] ) ) {
697 - $processed_images = count( $value['src'] );
698 - }
699 - if ( isset( $value['ignoredUrls'] ) && $value['ignoredUrls'] > $processed_images ) {
700 - $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>';
701 - $status = 'log';
702 - continue;
703 - }
704 831
705 - if ( $processed_images > 0 ) {
706 - $response = wp_remote_get( $value['src'][ rand( 0, $processed_images - 1 ) ], $args );
707 - if ( is_array( $response ) && ! is_wp_error( $response ) ) {
708 - $headers = $response['headers']; // array of http header lines
709 - $status_code = $response['response']['code'];
710 - if ( $status_code === 301 ) {
711 - $status = 'deactivated';
712 - $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>';
713 - break;
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'];
714 875 }
715 - if ( $status_code === 302 ) {
716 - if ( isset( $headers['x-redirect-o'] ) ) {
717 - $optimole_code = (int) $headers['x-redirect-o'];
718 - if ( $optimole_code === 1 ) {
719 - $status = 'log';
720 - $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>';
721 - }
722 - if ( $optimole_code === 4 ) {
723 - $status = 'log';
724 - $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>';
725 - }
726 - }
727 - }
728 876 }
729 877 }
730 878 }
731 - if ( $result === '' ) {
732 - $result = __( 'No issues detected, everything is running smoothly.', 'optimole-wp' );
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' ] );
733 896 }
734 897
735 - return $this->response( '<ul>' . $result . '</ul>', $status );
898 + $insert = Optml_Main::instance()->dam->insert_attachments( $images );
899 +
900 + return $this->response( $insert );
736 901 }
737 902
738 903 /**
739 - * Push existing image our servers.
904 + * Dismiss a notification (set the notification key to 'yes').
740 905 *
741 - * @param WP_REST_Request $request rest request object.
906 + * @param WP_REST_Request $request the incoming request.
907 + * @phpstan-param WP_REST_Request<array{key?: string}> $request
742 908 *
743 909 * @return WP_REST_Response
744 910 */
745 - public function offload_images( WP_REST_Request $request ) {
746 - $batch = 300;
747 - if ( ! empty( $request->get_param( 'batch' ) ) ) {
748 - $batch = $request->get_param( 'batch' );
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' );
749 916 }
750 - return $this->response( Optml_Media_Offload::upload_images( $batch ) );
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' ] );
751 925 }
926 +
752 927 /**
753 - * Rollback images to media library.
928 + * Store optimization data.
754 929 *
755 - * @param WP_REST_Request $request rest request object.
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
756 932 *
757 933 * @return WP_REST_Response
758 934 */
759 - public function rollback_images( WP_REST_Request $request ) {
760 - $batch = 300;
761 - if ( ! empty( $request->get_param( 'batch' ) ) ) {
762 - $batch = $request->get_param( 'batch' );
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' );
763 939 }
764 - return $this->response( Optml_Media_Offload::rollback_images( $batch ) );
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' );
765 1057 }
1058 +
766 1059 /**
767 - * Get total number of images.
1060 + * Method to register above fold data routes.
768 1061 *
769 - * @param WP_REST_Request $request rest request object.
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.
770 1072 *
1073 + * @param WP_REST_Request $request Rest request.
1074 + * @phpstan-param WP_REST_Request<array{id: int, action: string, status?: string}> $request
1075 + *
771 1076 * @return WP_REST_Response
772 1077 */
773 - public function number_of_library_images( WP_REST_Request $request ) {
774 - $action = 'offload_images';
775 - if ( ! empty( $request->get_param( 'action' ) ) ) {
776 - $action = $request->get_param( 'action' );
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 + }
777 1088 }
778 - return $this->response( Optml_Media_Offload::number_of_library_images( $action ) );
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;
779 1127 }
780 1128 }