PluginProbe
Elementor Website Builder – more than just a page builder / 4.2.0
Elementor Website Builder – more than just a page builder v4.2.0
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / modules / variables / classes / rest-api.php

rest-api.php in Elementor Website Builder – more than just a page builder 4.2.0, at modules/variables/classes/rest-api.php

618 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\Variables\Classes;
4
5 use Elementor\Modules\Variables\Storage\Exceptions\Type_Mismatch;
6 use WP_Error;
7 use Exception;
8 use WP_REST_Server;
9 use WP_REST_Request;
10 use Elementor\Plugin;
11 use WP_REST_Response;
12 use Elementor\Modules\Variables\Services\Variables_Service;
13 use Elementor\Modules\Variables\Module as Variables_Module;
14 use Elementor\Modules\Variables\Storage\Exceptions\VariablesLimitReached;
15 use Elementor\Modules\Variables\Storage\Exceptions\RecordNotFound;
16 use Elementor\Modules\Variables\Storage\Exceptions\DuplicatedLabel;
17 use Elementor\Modules\Variables\Storage\Exceptions\BatchOperationFailed;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit; // Exit if accessed directly.
21 }
22
23 class Rest_Api {
24 const API_NAMESPACE = 'elementor/v1';
25 const API_BASE = 'variables';
26 const HTTP_OK = 200;
27 const HTTP_CREATED = 201;
28 const HTTP_BAD_REQUEST = 400;
29 const HTTP_NOT_FOUND = 404;
30 const HTTP_SERVER_ERROR = 500;
31 const MAX_ID_LENGTH = 64;
32 const MAX_LABEL_LENGTH = 50;
33 const MAX_VALUE_LENGTH = 512;
34
35 private Variables_Service $service;
36
37 public function __construct( Variables_Service $service ) {
38 $this->service = $service;
39 }
40
41 public function enough_permissions_to_perform_ro_action() {
42 return current_user_can( 'edit_posts' );
43 }
44
45 public function enough_permissions_to_perform_rw_action() {
46 return current_user_can( 'manage_options' );
47 }
48
49 public function register_routes() {
50 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/list', [
51 'methods' => WP_REST_Server::READABLE,
52 'callback' => [ $this, 'get_variables' ],
53 'permission_callback' => [ $this, 'enough_permissions_to_perform_ro_action' ],
54 ] );
55
56 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/create', [
57 'methods' => WP_REST_Server::CREATABLE,
58 'callback' => [ $this, 'create_variable' ],
59 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ],
60 'args' => [
61 'type' => [
62 'required' => true,
63 'type' => 'string',
64 'validate_callback' => [ $this, 'is_valid_variable_type' ],
65 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
66 ],
67 'label' => [
68 'required' => true,
69 'type' => 'string',
70 'validate_callback' => [ $this, 'is_valid_variable_label' ],
71 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
72 ],
73 'value' => [
74 'required' => true,
75 'type' => 'string',
76 'validate_callback' => [ $this, 'is_valid_variable_value' ],
77 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
78 ],
79 ],
80 ] );
81
82 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/update', [
83 'methods' => WP_REST_Server::EDITABLE,
84 'callback' => [ $this, 'update_variable' ],
85 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ],
86 'args' => [
87 'id' => [
88 'required' => true,
89 'type' => 'string',
90 'validate_callback' => [ $this, 'is_valid_variable_id' ],
91 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
92 ],
93 'label' => [
94 'required' => true,
95 'type' => 'string',
96 'validate_callback' => [ $this, 'is_valid_variable_label' ],
97 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
98 ],
99 'value' => [
100 'required' => true,
101 'type' => 'string',
102 'validate_callback' => [ $this, 'is_valid_variable_value' ],
103 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
104 ],
105 'order' => [
106 'required' => false,
107 'type' => 'integer',
108 'validate_callback' => [ $this, 'is_valid_order' ],
109 ],
110 'type' => [
111 'required' => false,
112 'type' => 'string',
113 'validate_callback' => [ $this, 'is_valid_variable_type' ],
114 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
115 ],
116 ],
117 ] );
118
119 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/delete', [
120 'methods' => WP_REST_Server::EDITABLE,
121 'callback' => [ $this, 'delete_variable' ],
122 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ],
123 'args' => [
124 'id' => [
125 'required' => true,
126 'type' => 'string',
127 'validate_callback' => [ $this, 'is_valid_variable_id' ],
128 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
129 ],
130 ],
131 ] );
132
133 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/restore', [
134 'methods' => WP_REST_Server::EDITABLE,
135 'callback' => [ $this, 'restore_variable' ],
136 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ],
137 'args' => [
138 'id' => [
139 'required' => true,
140 'type' => 'string',
141 'validate_callback' => [ $this, 'is_valid_variable_id' ],
142 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
143 ],
144 'label' => [
145 'required' => false,
146 'type' => 'string',
147 'validate_callback' => [ $this, 'is_valid_variable_label' ],
148 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
149 ],
150 'value' => [
151 'required' => false,
152 'type' => 'string',
153 'validate_callback' => [ $this, 'is_valid_variable_value' ],
154 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
155 ],
156 'type' => [
157 'required' => false,
158 'type' => 'string',
159 'validate_callback' => [ $this, 'is_valid_variable_type' ],
160 'sanitize_callback' => [ $this, 'trim_and_sanitize_text_field' ],
161 ],
162 ],
163 ] );
164
165 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/batch', [
166 'methods' => WP_REST_Server::CREATABLE,
167 'callback' => [ $this, 'process_batch' ],
168 'permission_callback' => [ $this, 'enough_permissions_to_perform_rw_action' ],
169 'args' => [
170 'watermark' => [
171 'required' => true,
172 'type' => 'integer',
173 'validate_callback' => [ $this, 'is_valid_watermark' ],
174 ],
175 'operations' => [
176 'required' => true,
177 'type' => 'array',
178 'validate_callback' => [ $this, 'is_valid_operations_array' ],
179 ],
180 ],
181 ] );
182 }
183
184 public function trim_and_sanitize_text_field( $value ) {
185
186 return trim( sanitize_text_field( $value ) );
187 }
188
189 public function is_valid_variable_id( $id ) {
190 $id = trim( $id );
191
192 if ( empty( $id ) ) {
193 return new WP_Error(
194 'invalid_variable_id_empty',
195 __( 'ID cannot be empty', 'elementor' )
196 );
197 }
198
199 if ( self::MAX_ID_LENGTH < strlen( $id ) ) {
200 return new WP_Error( 'invalid_variable_id_length', sprintf(
201 /* translators: %d: Maximum ID length. */
202 __( 'ID cannot exceed %d characters', 'elementor' ),
203 self::MAX_ID_LENGTH
204 ) );
205 }
206
207 return true;
208 }
209
210 public function is_valid_variable_type( $type ) {
211 $allowed_types = array_keys( Variables_Module::instance()->get_variable_types_registry()->all() );
212
213 return in_array( $type, $allowed_types, true );
214 }
215
216 public function is_valid_variable_label( $label ) {
217 $label = trim( $label );
218
219 if ( empty( $label ) ) {
220 return new WP_Error(
221 'invalid_variable_label_empty',
222 __( 'Label cannot be empty', 'elementor' )
223 );
224 }
225
226 if ( self::MAX_LABEL_LENGTH < strlen( $label ) ) {
227 return new WP_Error( 'invalid_variable_label_length', sprintf(
228 /* translators: %d: Maximum label length. */
229 __( 'Label cannot exceed %d characters', 'elementor' ),
230 self::MAX_LABEL_LENGTH
231 ) );
232 }
233
234 return true;
235 }
236
237 public function is_valid_order( $order ) {
238 if ( ! is_numeric( $order ) || $order < 0 ) {
239 return new WP_Error(
240 'invalid_order',
241 __( 'Order must be a non-negative integer', 'elementor' )
242 );
243 }
244
245 return true;
246 }
247
248 public function is_valid_variable_value( $value ) {
249 $value = trim( $value );
250
251 if ( empty( $value ) ) {
252 return new WP_Error(
253 'invalid_variable_value_empty',
254 __( 'Value cannot be empty', 'elementor' )
255 );
256 }
257
258 if ( self::MAX_VALUE_LENGTH < strlen( $value ) ) {
259 return new WP_Error( 'invalid_variable_value_length', sprintf(
260 /* translators: %d: Maximum value length. */
261 __( 'Value cannot exceed %d characters', 'elementor' ),
262 self::MAX_VALUE_LENGTH
263 ) );
264 }
265
266 return true;
267 }
268
269 public function create_variable( WP_REST_Request $request ) {
270 try {
271 return $this->create_new_variable( $request );
272 } catch ( Exception $e ) {
273 return $this->error_response( $e );
274 }
275 }
276
277 protected function clear_cache() {
278 Plugin::$instance->files_manager->clear_cache();
279 }
280
281 private function create_new_variable( WP_REST_Request $request ) {
282 $type = $request->get_param( 'type' );
283 $label = $request->get_param( 'label' );
284 $value = $request->get_param( 'value' );
285
286 $result = $this->service->create( [
287 'type' => $type,
288 'label' => $label,
289 'value' => $value,
290 ] );
291
292 $this->clear_cache();
293
294 return $this->success_response( [
295 'variable' => $result['variable'],
296 'watermark' => $result['watermark'],
297 ], self::HTTP_CREATED );
298 }
299
300 public function update_variable( WP_REST_Request $request ) {
301 try {
302 return $this->update_existing_variable( $request );
303 } catch ( Exception $e ) {
304 return $this->error_response( $e );
305 }
306 }
307
308 private function update_existing_variable( WP_REST_Request $request ) {
309 $id = $request->get_param( 'id' );
310 $label = $request->get_param( 'label' );
311 $value = $request->get_param( 'value' );
312 $order = $request->get_param( 'order' );
313 $type = $request->get_param( 'type' );
314
315 $update_data = [
316 'label' => $label,
317 'value' => $value,
318 ];
319
320 if ( $type ) {
321 $update_data['type'] = $type;
322 }
323
324 if ( null !== $order ) {
325 $update_data['order'] = $order;
326 }
327
328 $result = $this->service->update( $id, $update_data );
329
330 $this->clear_cache();
331
332 return $this->success_response( [
333 'variable' => $result['variable'],
334 'watermark' => $result['watermark'],
335 ] );
336 }
337
338 public function delete_variable( WP_REST_Request $request ) {
339 try {
340 return $this->delete_existing_variable( $request );
341 } catch ( Exception $e ) {
342 return $this->error_response( $e );
343 }
344 }
345
346 private function delete_existing_variable( WP_REST_Request $request ) {
347 $id = $request->get_param( 'id' );
348
349 $result = $this->service->delete( $id );
350
351 $this->clear_cache();
352
353 return $this->success_response( [
354 'variable' => $result['variable'],
355 'watermark' => $result['watermark'],
356 ] );
357 }
358
359 public function restore_variable( WP_REST_Request $request ) {
360 try {
361 return $this->restore_existing_variable( $request );
362 } catch ( Exception $e ) {
363 return $this->error_response( $e );
364 }
365 }
366
367 private function restore_existing_variable( WP_REST_Request $request ) {
368 $id = $request->get_param( 'id' );
369
370 $overrides = [];
371
372 $label = $request->get_param( 'label' );
373
374 if ( $label ) {
375 $overrides['label'] = $label;
376 }
377
378 $value = $request->get_param( 'value' );
379
380 if ( $value ) {
381 $overrides['value'] = $value;
382 }
383
384 $type = $request->get_param( 'type' );
385
386 if ( $type ) {
387 $overrides['type'] = $type;
388 }
389
390 $result = $this->service->restore( $id, $overrides );
391
392 $this->clear_cache();
393
394 return $this->success_response( [
395 'variable' => $result['variable'],
396 'watermark' => $result['watermark'],
397 ] );
398 }
399
400 public function get_variables() {
401 try {
402 return $this->list_of_variables();
403 } catch ( Exception $e ) {
404 return $this->error_response( $e );
405 }
406 }
407
408 private function list_of_variables() {
409 $db_record = $this->service->load();
410
411 return $this->success_response( [
412 'variables' => $db_record['data'] ?? [],
413 'total' => count( $db_record['data'] ),
414 'watermark' => $db_record['watermark'],
415 ] );
416 }
417
418 private function success_response( $payload, $status_code = null ) {
419 return new WP_REST_Response( [
420 'success' => true,
421 'data' => $payload,
422 ], $status_code ?? self::HTTP_OK );
423 }
424
425 private function error_response( Exception $e ) {
426 if ( $e instanceof VariablesLimitReached ) {
427 return $this->prepare_error_response(
428 self::HTTP_BAD_REQUEST,
429 'invalid_variable_limit_reached',
430 __( 'Reached the maximum number of variables', 'elementor' )
431 );
432 }
433
434 if ( $e instanceof DuplicatedLabel ) {
435 return $this->prepare_error_response(
436 self::HTTP_BAD_REQUEST,
437 'duplicated_label',
438 __( 'Variable label already exists', 'elementor' )
439 );
440 }
441
442 if ( $e instanceof RecordNotFound ) {
443 return $this->prepare_error_response(
444 self::HTTP_NOT_FOUND,
445 'variable_not_found',
446 __( 'Variable not found', 'elementor' )
447 );
448 }
449
450 if ( $e instanceof Type_Mismatch ) {
451 return $this->prepare_error_response(
452 self::HTTP_BAD_REQUEST,
453 'type_mismatch',
454 $e->getMessage()
455 );
456 }
457
458 return $this->prepare_error_response(
459 self::HTTP_SERVER_ERROR,
460 'unexpected_server_error',
461 __( 'Unexpected server error', 'elementor' )
462 );
463 }
464
465 private function prepare_error_response( $status_code, $error, $message ) {
466 return new WP_REST_Response( [
467 'code' => $error,
468 'message' => $message,
469 'data' => [
470 'status' => $status_code,
471 ],
472 ], $status_code );
473 }
474
475 public function is_valid_watermark( $watermark ) {
476 if ( ! is_numeric( $watermark ) || $watermark < 0 ) {
477 return new WP_Error(
478 'invalid_watermark',
479 __( 'Watermark must be a non-negative integer', 'elementor' )
480 );
481 }
482
483 return true;
484 }
485
486 public function is_valid_operations_array( $operations ) {
487 if ( ! is_array( $operations ) || empty( $operations ) ) {
488 return new WP_Error(
489 'invalid_operations_empty',
490 __( 'Operations array cannot be empty', 'elementor' )
491 );
492 }
493
494 foreach ( $operations as $index => $operation ) {
495 if ( ! is_array( $operation ) || ! isset( $operation['type'] ) ) {
496 $sanitized_index = absint( $index );
497 return new WP_Error(
498 'invalid_operation_structure',
499 sprintf(
500 /* translators: %d: operation index */
501 __( 'Invalid operation structure at index %d', 'elementor' ),
502 $sanitized_index
503 )
504 );
505 }
506
507 $allowed_types = [ 'create', 'update', 'delete', 'restore', 'reorder' ];
508
509 if ( ! in_array( $operation['type'], $allowed_types, true ) ) {
510 $sanitized_index = absint( $index );
511 return new WP_Error(
512 'invalid_operation_type',
513 sprintf(
514 /* translators: %d: operation index */
515 __( 'Invalid operation type at index %d', 'elementor' ),
516 $sanitized_index
517 )
518 );
519 }
520 }
521
522 return true;
523 }
524
525 public function process_batch( WP_REST_Request $request ) {
526 try {
527 return $this->process_batch_operations( $request );
528 } catch ( Exception $e ) {
529 return $this->batch_error_response( $e );
530 }
531 }
532
533 private function process_batch_operations( WP_REST_Request $request ) {
534 $operations = $request->get_param( 'operations' );
535
536 $result = $this->service->process_batch( $operations );
537
538 $this->clear_cache();
539
540 return $this->success_response( $result );
541 }
542
543
544 private function batch_error_response( Exception $e ) {
545 if ( $e instanceof BatchOperationFailed ) {
546 $error_details = $e->getErrorDetails();
547 $batch_error_context = $this->determine_batch_error_context( $error_details );
548
549 return new WP_REST_Response( [
550 'success' => false,
551 'code' => $batch_error_context['code'],
552 'message' => $batch_error_context['message'],
553 'data' => $batch_error_context['filtered_errors'],
554 ], self::HTTP_BAD_REQUEST );
555 }
556
557 return $this->error_response( $e );
558 }
559
560 private function determine_batch_error_context( array $error_details ) {
561 $error_config = [
562 'invalid_variable_limit_reached' => [
563 'batch_code' => 'batch_variables_limit_reached',
564 'batch_message' => __( 'Batch operation failed: Reached the maximum number of variables', 'elementor' ),
565 'status' => self::HTTP_BAD_REQUEST,
566 'message' => __( 'Reached the maximum number of variables', 'elementor' ),
567 ],
568 'duplicated_label' => [
569 'batch_code' => 'batch_duplicated_label',
570 'batch_message' => __( 'Batch operation failed: Variable labels already exist', 'elementor' ),
571 'status' => self::HTTP_BAD_REQUEST,
572 'message' => __( 'Variable label already exists', 'elementor' ),
573 ],
574 'variable_not_found' => [
575 'batch_code' => 'batch_variables_not_found',
576 'batch_message' => __( 'Batch operation failed: Variables not found', 'elementor' ),
577 'status' => self::HTTP_NOT_FOUND,
578 'message' => __( 'Variable not found', 'elementor' ),
579 ],
580 ];
581
582 $grouped_errors = [];
583
584 foreach ( $error_details as $id => $error_detail ) {
585 $error_code = $error_detail['code'] ?? '';
586
587 if ( isset( $error_config[ $error_code ] ) ) {
588 $config = $error_config[ $error_code ];
589 $grouped_errors[ $error_code ][ $id ] = [
590 'status' => $config['status'],
591 'message' => $config['message'],
592 ];
593 } else {
594 $grouped_errors['unknown'][ $id ] = [
595 'status' => self::HTTP_SERVER_ERROR,
596 'message' => $error_detail['message'] ?? __( 'Unexpected error', 'elementor' ),
597 ];
598 }
599 }
600
601 foreach ( $error_config as $error_code => $config ) {
602 if ( ! empty( $grouped_errors[ $error_code ] ) ) {
603 return [
604 'code' => $config['batch_code'],
605 'message' => $config['batch_message'],
606 'filtered_errors' => $grouped_errors[ $error_code ],
607 ];
608 }
609 }
610
611 return [
612 'code' => 'batch_operation_failed',
613 'message' => __( 'Batch operation failed', 'elementor' ),
614 'filtered_errors' => $grouped_errors['unknown'] ?? [],
615 ];
616 }
617 }
618