PluginProbe
Elementor Website Builder – more than just a page builder / 3.34.3
Elementor Website Builder – more than just a page builder v3.34.3
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 3.34.3, at modules/variables/classes/rest-api.php

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