PluginProbe
Elementor Website Builder – more than just a page builder / 3.31.2
Elementor Website Builder – more than just a page builder v3.31.2
4.3.0-beta3 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 All 452 releases
← All changes | modules/global-classes/global-classes-rest-api.php +46 -289 4.3.0-beta33.31.2 View file →
@@ -1,14 +1,12 @@
1 1 <?php
2 2
3 3 namespace Elementor\Modules\GlobalClasses;
4 4
5 -use Elementor\Core\Kits\Documents\Kit;
6 -use Elementor\Core\Utils\Api\Error_Builder;
7 -use Elementor\Core\Utils\Api\Response_Builder;
5 +use Elementor\Modules\GlobalClasses\Usage\Applied_Global_Classes_Usage;
6 +use Elementor\Modules\GlobalClasses\Utils\Error_Builder;
7 +use Elementor\Modules\GlobalClasses\Utils\Response_Builder;
8 8 use Elementor\Modules\GlobalClasses\Database\Migrations\Add_Capabilities;
9 -use Elementor\Modules\GlobalClasses\Usage\Applied_Global_Classes_Usage;
10 -use Elementor\Plugin;
11 9
12 10 if ( ! defined( 'ABSPATH' ) ) {
13 11 exit; // Exit if accessed directly.
14 12 }
@@ -13,61 +11,34 @@
13 11 exit; // Exit if accessed directly.
14 12 }
15 13
16 14 class Global_Classes_REST_API {
15 +
17 16 const API_NAMESPACE = 'elementor/v1';
18 17 const API_BASE = 'global-classes';
19 18 const API_BASE_USAGE = self::API_BASE . '/usage';
20 - const API_BASE_POST = self::API_BASE . '/post';
21 - const API_BASE_STYLES = self::API_BASE . '/styles';
22 - const MAX_ITEMS = 1000;
23 - private ?Global_Classes_Repository $repository = null;
24 - private ?Global_Classes_Relations $relations = null;
25 - private ?Kit $kit = null;
19 + const MAX_ITEMS = 50;
26 20
21 + private $repository = null;
22 +
27 23 public function register_hooks() {
28 24 add_action( 'rest_api_init', fn() => $this->register_routes() );
29 25 }
30 26
31 - public function invalidate_cache() {
32 - $this->kit = null;
33 - $this->repository = null;
34 - $this->relations = null;
35 - }
36 -
37 - private function get_kit(): ?Kit {
38 - if ( ! $this->kit ) {
39 - $this->kit = Plugin::$instance->kits_manager->get_active_kit();
40 - }
41 -
42 - return $this->kit;
43 - }
44 -
45 27 private function get_repository() {
46 28 if ( ! $this->repository ) {
47 - $this->repository = new Global_Classes_Repository( $this->get_kit() );
29 + $this->repository = new Global_Classes_Repository();
48 30 }
49 31
50 32 return $this->repository;
51 33 }
52 34
53 - private function get_classes_relations(): Global_Classes_Relations {
54 - if ( ! $this->relations ) {
55 - $this->relations = new Global_Classes_Relations();
56 - }
57 -
58 - return $this->relations;
59 - }
60 -
61 35 private function register_routes() {
62 - // cache invalidation at this point is solely for tests, in particular - Test_Global_Classes_Rest_Api
63 - $this->invalidate_cache();
64 -
65 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
36 + register_rest_route(self::API_NAMESPACE, '/' . self::API_BASE, [
66 37 [
67 38 'methods' => 'GET',
68 39 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->all( $request ) ),
69 - 'permission_callback' => fn() => is_user_logged_in(),
40 + 'permission_callback' => fn() => true,
70 41 'args' => [
71 42 'context' => [
72 43 'type' => 'string',
73 44 'required' => false,
@@ -78,60 +49,12 @@
78 49 ],
79 50 ],
80 51 ],
81 52 ],
82 - ] );
53 + ]);
83 54
84 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_POST, [
55 + register_rest_route(self::API_NAMESPACE, '/' . self::API_BASE_USAGE, [
85 56 [
86 - 'methods' => 'GET',
87 - 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->styles_for_post( $request ) ),
88 - 'permission_callback' => fn() => is_user_logged_in(),
89 - 'args' => [
90 - 'context' => [
91 - 'type' => 'string',
92 - 'required' => false,
93 - 'default' => Global_Classes_Repository::CONTEXT_FRONTEND,
94 - 'enum' => [
95 - Global_Classes_Repository::CONTEXT_FRONTEND,
96 - Global_Classes_Repository::CONTEXT_PREVIEW,
97 - ],
98 - ],
99 - 'post_id' => [
100 - 'type' => 'integer',
101 - 'required' => true,
102 - ],
103 - ],
104 - ],
105 - ] );
106 -
107 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_STYLES, [
108 - [
109 - 'methods' => 'GET',
110 - 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->styles_by_ids( $request ) ),
111 - 'permission_callback' => fn() => is_user_logged_in(),
112 - 'args' => [
113 - 'context' => [
114 - 'type' => 'string',
115 - 'required' => false,
116 - 'default' => Global_Classes_Repository::CONTEXT_FRONTEND,
117 - 'enum' => [
118 - Global_Classes_Repository::CONTEXT_FRONTEND,
119 - Global_Classes_Repository::CONTEXT_PREVIEW,
120 - ],
121 - ],
122 - 'ids' => [
123 - 'type' => 'string',
124 - 'required' => true,
125 - 'description' => 'Comma-separated list of global class IDs',
126 - ],
127 - ],
128 - ],
129 - ] );
130 -
131 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_USAGE, [
132 - [
133 - 'methods' => 'GET',
134 57 'callback' => fn() => $this->route_wrapper( fn() => $this->get_usage() ),
135 58 'permission_callback' => fn() => current_user_can( 'manage_options' ),
136 59 'args' => [
137 60 'context' => [
@@ -144,11 +67,11 @@
144 67 ],
145 68 ],
146 69 ],
147 70 ],
148 - ] );
71 + ]);
149 72
150 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
73 + register_rest_route(self::API_NAMESPACE, '/' . self::API_BASE, [
151 74 [
152 75 'methods' => 'PUT',
153 76 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->put( $request ) ),
154 77 'permission_callback' => fn() => current_user_can( Add_Capabilities::UPDATE_CLASS ),
@@ -181,12 +104,8 @@
181 104 'type' => 'array',
182 105 'required' => true,
183 106 'items' => [ 'type' => 'string' ],
184 107 ],
185 - 'order' => [
186 - 'type' => 'boolean',
187 - 'required' => false,
188 - ],
189 108 ],
190 109 ],
191 110 'items' => [
192 111 'required' => true,
@@ -222,82 +141,21 @@
222 141 ],
223 142 ],
224 143 ],
225 144 ],
226 - ] );
145 + ]);
227 146 }
228 147
229 148 private function all( \WP_REST_Request $request ) {
230 149 $context = $request->get_param( 'context' );
231 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
232 - $label_by_id = $this->get_repository()->set_preview( $is_preview )->all_labels();
233 - $list = [];
234 150
235 - foreach ( $label_by_id as $id => $label ) {
236 - $list[] = [
237 - 'id' => $id,
238 - 'label' => $label,
239 - ];
240 - }
151 + $classes = $this->get_repository()->context( $context )->all();
241 152
242 - return Response_Builder::make( $list )->build();
243 - }
244 -
245 - private function styles_for_post( \WP_REST_Request $request ) {
246 - $context = $request->get_param( 'context' );
247 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
248 - $post_id = (int) $request->get_param( 'post_id' );
249 -
250 - $document_class_ids = $this->get_classes_relations()->set_preview( $is_preview )->get_styles_by_post( $post_id );
251 -
252 - if ( empty( $document_class_ids ) ) {
253 - return Response_Builder::make( (object) [] )->set_meta( [ 'order' => [] ] )->build();
254 - }
255 -
256 - $repository = $this->get_repository()->set_preview( $is_preview );
257 - $global_order = array_keys( $repository->all_labels() );
258 - $filtered_order = array_values( array_intersect( $global_order, $document_class_ids ) );
259 - $items = $repository->get_by_ids( $document_class_ids );
260 -
261 - $result = [];
262 -
263 - foreach ( $document_class_ids as $id ) {
264 - $result[ $id ] = $items[ $id ] ?? null;
265 - }
266 -
267 - return Response_Builder::make( (object) $result )
268 - ->set_meta( [ 'order' => $filtered_order ] )
153 + return Response_Builder::make( (object) $classes->get_items()->all() )
154 + ->set_meta( [ 'order' => $classes->get_order()->all() ] )
269 155 ->build();
270 156 }
271 157
272 - private function styles_by_ids( \WP_REST_Request $request ) {
273 - $context = $request->get_param( 'context' );
274 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
275 - $ids_param = $request->get_param( 'ids' );
276 -
277 - $requested_ids = array_map( 'trim', explode( ',', $ids_param ) );
278 - $requested_ids = array_filter( $requested_ids );
279 -
280 - if ( empty( $requested_ids ) ) {
281 - return Response_Builder::make( (object) [] )->set_meta( [ 'order' => [] ] )->build();
282 - }
283 -
284 - $repository = $this->get_repository()->set_preview( $is_preview );
285 - $global_order = array_keys( $repository->all_labels() );
286 - $filtered_order = array_values( array_intersect( $global_order, $requested_ids ) );
287 - $items = $repository->get_by_ids( $requested_ids );
288 -
289 - $result = [];
290 -
291 - foreach ( $requested_ids as $id ) {
292 - $result[ $id ] = $items[ $id ] ?? null;
293 - }
294 -
295 - return Response_Builder::make( (object) $result )
296 - ->set_meta( [ 'order' => $filtered_order ] )
297 - ->build();
298 - }
299 -
300 158 private function get_usage() {
301 159 $classes_usage = ( new Applied_Global_Classes_Usage() )->get_detailed_usage();
302 160
303 161 return Response_Builder::make( (object) $classes_usage )->build();
@@ -303,76 +161,38 @@
303 161 return Response_Builder::make( (object) $classes_usage )->build();
304 162 }
305 163
306 164 private function put( \WP_REST_Request $request ) {
307 - $context = $request->get_param( 'context' );
308 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
309 - $changes = $request->get_param( 'changes' ) ?? [];
310 - $added_ids = $changes['added'] ?? [];
311 - $deleted_ids = $changes['deleted'] ?? [];
312 - $order = $request->get_param( 'order' ) ?? [];
165 + $parser = Global_Classes_Parser::make();
313 166
314 - $repository = $this->get_repository()->set_preview( $is_preview );
315 - $all_label_by_id = $repository->all_labels();
316 - $existing_label_list = $this->global_classes_existing_label_list( $all_label_by_id, $deleted_ids );
317 - $total_count = count( $all_label_by_id ) - count( $deleted_ids ) + count( $added_ids );
167 + $items_result = $parser->parse_items(
168 + $request->get_param( 'items' )
169 + );
318 170
319 - $parser = Global_Classes_Parser::make();
320 - $items_result = $parser->parse_items( $request->get_param( 'items' ) ?? [] );
171 + $items_count = count( $items_result->unwrap() );
321 172
322 - if ( ! $items_result->is_valid() ) {
323 - return Error_Builder::make( 'invalid_items' )
173 + if ( $items_count >= static::MAX_ITEMS ) {
174 + return Error_Builder::make( 'global_classes_limit_exceeded' )
324 175 ->set_status( 400 )
325 - ->set_message( 'Invalid items: ' . $items_result->errors()->to_string() )
176 + ->set_message(sprintf(
177 + __( 'Global classes limit exceeded. Maximum allowed: %d', 'elementor' ),
178 + static::MAX_ITEMS
179 + ))
326 180 ->build();
327 181 }
328 182
329 - $touched_items = $items_result->unwrap();
330 -
331 - if ( $total_count > self::MAX_ITEMS ) {
332 - return Error_Builder::make( 'global_classes_limit_exceeded' )
183 + if ( ! $items_result->is_valid() ) {
184 + return Error_Builder::make( 'invalid_items' )
333 185 ->set_status( 400 )
334 - ->set_meta( [
335 - 'current_count' => $total_count,
336 - 'max_allowed' => self::MAX_ITEMS,
337 - ] )
338 - ->set_message( sprintf(
339 - /* translators: %d: Maximum allowed items. */
340 - __( 'Global classes limit exceeded. Maximum allowed: %d', 'elementor' ),
341 - self::MAX_ITEMS
342 - ) )
186 + ->set_message( 'Invalid items: ' . $items_result->errors()->to_string() )
343 187 ->build();
344 188 }
345 189
346 - $duplicated_labels = Global_Classes_Parser::check_for_duplicate_labels(
347 - $all_label_by_id,
348 - $deleted_ids,
349 - $touched_items,
350 - $added_ids
190 + $order_result = $parser->parse_order(
191 + $request->get_param( 'order' ),
192 + $items_result->unwrap()
351 193 );
352 194
353 - $duplicate_validation_result = null;
354 -
355 - if ( ! empty( $duplicated_labels ) ) {
356 - $modified_labels = $this->handle_duplicates( $duplicated_labels, $existing_label_list );
357 - $duplicate_validation_result = $modified_labels;
358 -
359 - foreach ( $modified_labels as $item_id => $labels ) {
360 - $touched_items[ $item_id ]['label'] = $labels['modified'];
361 - }
362 - }
363 -
364 - $final_item_ids = array_keys( $this->merge_touched_with_existing_labels( $all_label_by_id, $touched_items, $deleted_ids ) );
365 -
366 - $final_item_ids_set = array_flip( $final_item_ids );
367 - $order_set = array_flip( $order );
368 -
369 - $order = array_values( array_filter( $order, fn( $id ) => isset( $final_item_ids_set[ $id ] ) ) );
370 - $missing_from_order = array_values( array_filter( $final_item_ids, fn( $id ) => ! isset( $order_set[ $id ] ) ) );
371 - $order = array_merge( $order, $missing_from_order );
372 -
373 - $order_result = $parser->parse_order( $order, $final_item_ids );
374 -
375 195 if ( ! $order_result->is_valid() ) {
376 196 return Error_Builder::make( 'invalid_order' )
377 197 ->set_status( 400 )
378 198 ->set_message( 'Invalid order: ' . $order_result->errors()->to_string() )
@@ -378,68 +198,24 @@
378 198 ->set_message( 'Invalid order: ' . $order_result->errors()->to_string() )
379 199 ->build();
380 200 }
381 201
382 - $repository->apply_changes( $touched_items, [
383 - 'added' => $added_ids,
384 - 'deleted' => $changes['deleted'] ?? [],
385 - 'modified' => $changes['modified'] ?? [],
386 - 'order' => isset( $changes['order'] ) && $changes['order'], // boolean indicating if the order has changed
387 - ], $order_result->unwrap() );
202 + $repository = $this->get_repository()
203 + ->context( $request->get_param( 'context' ) );
388 204
389 - if ( $duplicate_validation_result ) {
390 - return Response_Builder::make( [
391 - 'code' => 'DUPLICATED_LABEL',
392 - 'modifiedLabels' => $duplicate_validation_result,
393 - ] )->build();
394 - }
205 + $changes_resolver = Global_Classes_Changes_Resolver::make(
206 + $repository,
207 + $request->get_param( 'changes' ) ?? [],
208 + );
395 209
210 + $repository->put(
211 + $changes_resolver->resolve_items( $items_result->unwrap() ),
212 + $changes_resolver->resolve_order( $order_result->unwrap() ),
213 + );
214 +
396 215 return Response_Builder::make()->no_content()->build();
397 216 }
398 217
399 - private function global_classes_existing_label_list( array $label_by_id, array $deleted_ids ): array {
400 - $labels = [];
401 -
402 - foreach ( $label_by_id as $id => $label ) {
403 - if ( in_array( $id, $deleted_ids, true ) ) {
404 - continue;
405 - }
406 -
407 - $labels[] = $label;
408 - }
409 -
410 - return $labels;
411 - }
412 -
413 - private function merge_touched_with_existing_labels( array $label_by_id, array $touched_items, array $deleted_ids ): array {
414 - $final = [];
415 -
416 - foreach ( $label_by_id as $id => $label ) {
417 - if ( in_array( $id, $deleted_ids, true ) ) {
418 - continue;
419 - }
420 -
421 - if ( isset( $touched_items[ $id ] ) ) {
422 - $final[ $id ] = $touched_items[ $id ];
423 - } else {
424 - $final[ $id ] = [
425 - 'id' => $id,
426 - 'label' => $label,
427 - 'type' => 'class',
428 - 'variants' => [],
429 - ];
430 - }
431 - }
432 -
433 - foreach ( $touched_items as $id => $item ) {
434 - if ( ! isset( $final[ $id ] ) ) {
435 - $final[ $id ] = $item;
436 - }
437 - }
438 -
439 - return $final;
440 - }
441 -
442 218 private function route_wrapper( callable $cb ) {
443 219 try {
444 220 $response = $cb();
445 221 } catch ( \Exception $e ) {
@@ -448,25 +224,6 @@
448 224 ->build();
449 225 }
450 226
451 227 return $response;
452 - }
453 -
454 - private function handle_duplicates( array $duplicate_labels, array $existing_labels ) {
455 -
456 - $modified_labels = [];
457 -
458 - foreach ( $duplicate_labels as $duplicate_label ) {
459 - $item_id = $duplicate_label['item_id'];
460 - $original_label = $duplicate_label['label'];
461 -
462 - $modified_label = Global_Classes_Labels::generate_unique_label( $original_label, $existing_labels );
463 -
464 - $modified_labels[ $item_id ] = [
465 - 'original' => $original_label,
466 - 'modified' => $modified_label,
467 - ];
468 - }
469 -
470 - return $modified_labels;
471 228 }
472 229 }