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