PluginProbe
Elementor Website Builder – more than just a page builder / 3.27.4
Elementor Website Builder – more than just a page builder v3.27.4
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-repository.php +59 -463 4.1.33.27.4 View file →
@@ -1,12 +1,12 @@
1 1 <?php
2 2 namespace Elementor\Modules\GlobalClasses;
3 3
4 4 use Elementor\Core\Kits\Documents\Kit;
5 -use Elementor\Modules\DesignSystemSync\Classes\Global_Classes_Sync_Map;
6 -use Elementor\Modules\GlobalClasses\Concerns\Has_Kit_Dependency;
7 -use Elementor\Modules\GlobalClasses\Concerns\Has_Preview_Context;
8 -use Elementor\Modules\GlobalClasses\Utils\Global_Class_Data_Normalizer;
5 +use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser;
6 +use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
7 +use Elementor\Modules\AtomicWidgets\Styles\Utils as Atomic_Styles_Utils;
8 +use Elementor\Plugin;
9 9
10 10 if ( ! defined( 'ABSPATH' ) ) {
11 11 exit; // Exit if accessed directly.
12 12 }
@@ -11,506 +11,102 @@
11 11 exit; // Exit if accessed directly.
12 12 }
13 13
14 14 class Global_Classes_Repository {
15 - use Has_Kit_Dependency;
16 - use Has_Preview_Context;
15 + const META_KEY = '_elementor_global_classes';
17 16
18 - const META_KEY_FRONTEND = '_elementor_global_classes';
19 - const META_KEY_PREVIEW = '_elementor_global_classes_preview';
20 -
21 - const CONTEXT_FRONTEND = 'frontend';
22 - const CONTEXT_PREVIEW = 'preview';
23 -
24 - const READ_BATCH_SIZE = 100;
25 - const PERSIST_BATCH_SIZE = 100;
26 -
27 - protected array $context_keys = [
28 - 'event' => [
29 - 'frontend' => self::CONTEXT_FRONTEND,
30 - 'preview' => self::CONTEXT_PREVIEW,
31 - ],
32 - 'meta_key' => [
33 - 'frontend' => self::META_KEY_FRONTEND,
34 - 'preview' => self::META_KEY_PREVIEW,
35 - ],
36 - ];
37 -
38 - private ?Global_Classes $cache = null;
39 -
40 - public function __construct( ?Kit $kit = null ) {
41 - if ( null !== $kit ) {
42 - $this->set_kit( $kit );
43 - }
17 + public static function make(): Global_Classes_Repository {
18 + return new self();
44 19 }
45 20
46 - public static function make( ?Kit $kit = null ): Global_Classes_Repository {
47 - return new self( $kit );
48 - }
21 + public function all() {
22 + $all = Plugin::$instance->kits_manager->get_active_kit()->get_json_meta( self::META_KEY );
49 23
50 - protected function on_preview_change(): void {
51 - $this->cache = null;
24 + return Global_Classes::make( $all['items'] ?? [], $all['order'] ?? [] );
52 25 }
53 26
54 - /**
55 - * This method may be too heavy to use
56 - * Be mindful as this call would cause the server to freeze for as much time as needed until it fetches
57 - * all global classes
58 - */
59 - public function all( bool $force = false ): Global_Classes {
60 - if ( ! $force && null !== $this->cache ) {
61 - return $this->cache;
62 - }
63 -
64 - $this->cache = $this->all_from_posts();
65 -
66 - return $this->cache;
27 + public function get( string $id ) {
28 + return $this->all()->get_items()->get( $id );
67 29 }
68 30
69 - public function all_labels(): array {
70 - return $this->labels()->get_ordered_labels();
71 - }
31 + public function delete( string $id ) {
32 + $all = $this->all();
72 33
73 - public function get_order(): array {
74 - return Global_Classes_Order::make( $this->get_kit() )->set_preview( $this->is_preview() )->get_order();
75 - }
76 -
77 - public function update_order_and_labels( array $order, array $new_labels ): void {
78 - Global_Classes_Order::make( $this->get_kit() )
79 - ->set_preview( $this->is_preview() )
80 - ->set_order( $order );
81 -
82 - $labels = $this->labels();
83 - $existing_labels = $labels->get_labels();
84 -
85 - foreach ( $new_labels as $id => $label ) {
86 - $existing_labels[ $id ] = $label;
34 + if ( ! isset( $all->get_items()[ $id ] ) ) {
35 + throw new \Exception( "Global class with id ${id} not found" );
87 36 }
88 37
89 - $labels->set_labels( $existing_labels );
90 -
91 - if ( ! $this->is_preview() ) {
92 - Global_Classes_Order::make( $this->get_kit() )
93 - ->set_preview( true )
94 - ->set_order( $order );
95 -
96 - $this->clear_preview_labels_for_ids( array_keys( $new_labels ) );
97 - }
98 -
99 - $this->cache = null;
38 + Plugin::$instance->kits_manager->get_active_kit()->update_json_meta( self::META_KEY, [
39 + 'items' => $all->get_items()->except( [ $id ] )->all(),
40 + 'order' => $all->get_order()->filter( fn( $item ) => $item !== $id )->all(),
41 + ] );
100 42 }
101 43
102 - private function labels(): Global_Classes_Labels {
103 - return Global_Classes_Labels::make( $this->get_kit() )->set_preview( $this->is_preview() );
104 - }
44 + public function put( string $id, array $value ) {
45 + $all = $this->all();
105 46
106 - public function get( string $class_id ): ?array {
107 - $post = Global_Class_Post::find_by_class_id( $class_id, $this->is_preview() );
47 + unset( $value['id'] );
108 48
109 - return $post ? $post->to_array() : null;
110 - }
111 -
112 - public function get_by_ids( array $class_ids ): array {
113 - if ( empty( $class_ids ) ) {
114 - return [];
49 + if ( ! isset( $all->get_items()[ $id ] ) ) {
50 + throw new \Exception( "Global class with id {$id} not found" );
115 51 }
116 52
117 - $post_ids = Global_Classes_Post_IDs::make( $this->get_kit() )->get_post_ids( $class_ids );
118 - $items = [];
119 -
120 - foreach ( $post_ids as $class_id => $post_id ) {
121 - $post = Global_Class_Post::from_post_id( $post_id, $this->is_preview() );
122 -
123 - if ( $post ) {
124 - $items[ $class_id ] = $post->to_array();
125 - }
53 + if ( $value === $all->get_items()[ $id ] ) {
54 + return $value;
126 55 }
127 56
128 - return $items;
129 - }
130 -
131 - public function apply_changes( array $touched_items, array $changes, array $order ): void {
132 - $labels = $this->labels();
133 - $before = $labels->get_labels();
134 - $is_preview = $this->is_preview();
135 - $to_delete = $changes['deleted'] ?? [];
136 - $to_create = $changes['added'] ?? [];
137 - $to_update = $changes['modified'] ?? [];
138 - $order_changed = isset( $changes['order'] ) && $changes['order'];
139 -
140 - $final_label_map = [];
141 - foreach ( $order as $id ) {
142 - if ( isset( $touched_items[ $id ] ) ) {
143 - $final_label_map[ $id ] = $touched_items[ $id ]['label'];
144 - } elseif ( isset( $before[ $id ] ) ) {
145 - $final_label_map[ $id ] = $before[ $id ];
146 - }
147 - }
148 -
149 - $affected_post_ids = $this->get_posts_affected_by_deletion( $to_delete );
150 -
151 - $this->persist_class_batch_mutations( $to_delete, $to_create, $to_update, $touched_items, $is_preview );
152 -
153 - $classes_order = Global_Classes_Order::make( $this->get_kit() )->set_preview( $this->is_preview() );
154 - $classes_order->set_order( $order );
155 - $labels->set_labels( $final_label_map );
156 -
157 - if ( ! $is_preview ) {
158 - Global_Classes_Sync_Map::make( $this->get_kit() )->apply_changes( $touched_items, $to_delete );
159 - Global_Classes_Order::make( $this->get_kit() )
160 - ->set_preview( true )
161 - ->set_order( $order );
162 -
163 - $this->bulk_clear_preview_meta( array_values( $to_update ) );
164 - $this->clear_preview_labels_for_ids( array_merge(
165 - array_values( $to_create ),
166 - array_values( $to_update ),
167 - array_values( $to_delete )
168 - ) );
169 - }
170 -
171 - $this->cache = null;
172 - $this->flush_runtime_cache();
173 -
174 - do_action( 'elementor/global_classes/update', $this->get_context_key( 'event' ), [
175 - 'added' => $to_create,
176 - 'deleted' => $to_delete,
177 - 'modified' => $to_update,
178 - 'order' => $order_changed,
179 - 'affected_post_ids' => $affected_post_ids,
57 + $value = Plugin::$instance->kits_manager->get_active_kit()->update_json_meta( self::META_KEY, [
58 + 'items' => $all->get_items()->merge( [ $id => $value ] )->all(),
59 + 'order' => $all->get_order()->all(),
180 60 ] );
181 61
182 - if ( ! empty( $to_delete ) && ! $is_preview ) {
183 - do_action(
184 - 'elementor/global_classes/cleanup',
185 - $to_delete,
186 - $affected_post_ids
187 - );
62 + if ( ! $value ) {
63 + throw new \Exception( 'Failed to update global class' );
188 64 }
189 - }
190 65
191 - public function each_item( callable $cb, bool $skip_migration = false, int $batch_size = self::READ_BATCH_SIZE ): void {
192 - $order = Global_Classes_Order::make( $this->get_kit() )->get_order();
193 -
194 - if ( empty( $order ) ) {
195 - return;
196 - }
197 -
198 - foreach ( array_chunk( $order, $batch_size ) as $chunk ) {
199 - foreach ( $this->iterate_class_posts_for_ids( $chunk ) as $class_post ) {
200 - $cb( $class_post->to_array( $skip_migration ) );
201 - }
202 - }
66 + return $this->get( $id );
203 67 }
204 68
205 - public function put( array $items, array $order ) {
206 - $current_ids = Global_Classes_Order::make( $this->get_kit() )
207 - ->set_preview( $this->is_preview() )
208 - ->get_order();
69 + public function create( array $value ) {
70 + $all = $this->all();
71 + $id = $this->generate_global_class_id();
209 72
210 - $new_ids = array_keys( $items );
211 - $current_order_string = implode( ';', $current_ids );
212 - $deleted_class_ids = array_values( array_diff( $current_ids, $new_ids ) );
73 + $value['id'] = $id;
213 74
214 - $changes = [
215 - 'added' => array_values( array_diff( $new_ids, $current_ids ) ),
216 - 'deleted' => $deleted_class_ids,
217 - 'modified' => array_values( array_intersect( $new_ids, $current_ids ) ),
218 - 'order' => implode( ';', $order ) !== $current_order_string,
219 - ];
75 + $updated = Plugin::$instance->kits_manager->get_active_kit()->update_json_meta( self::META_KEY, [
76 + 'items' => $all->get_items()->merge( [ $id => $value ] )->all(),
77 + 'order' => $all->get_order()->push( $id )->all(),
78 + ] );
220 79
221 - /**
222 - * We collect all affected ids before the put_to_posts execution
223 - * as the update mechanism would handle the Global_Classes_Relations as it iterates over the update batches
224 - * So once we get to the cleanup phase - we would no longer have the relevant relations
225 - *
226 - * On top of that - by collecting all affected posts in advance, means we would iterate over each document's elements only once
227 - * (as the alternative would be to trigger the cleanup per removed class, but that means we may end up iterating over the same document N times, if all N styles are used in it)
228 - */
229 - $affected_post_ids = $this->get_posts_affected_by_deletion( $deleted_class_ids );
230 -
231 - $this->put_to_posts( $items, $order, $current_ids );
232 -
233 - $this->cache = null;
234 -
235 - $changes['affected_post_ids'] = $affected_post_ids;
236 -
237 - do_action( 'elementor/global_classes/update', $this->get_context_key( 'event' ), $changes );
238 -
239 - if ( ! empty( $deleted_class_ids ) && ! $this->is_preview() ) {
240 - do_action(
241 - 'elementor/global_classes/cleanup',
242 - $deleted_class_ids,
243 - $affected_post_ids
244 - );
80 + if ( ! $updated ) {
81 + throw new \Exception( 'Failed to create global class' );
245 82 }
246 - }
247 83
248 - private function get_posts_affected_by_deletion( array $deleted_class_ids ): array {
249 - if ( empty( $deleted_class_ids ) ) {
250 - return [];
251 - }
252 -
253 - $relations = new Global_Classes_Relations();
254 - $post_ids = [];
255 -
256 - foreach ( $deleted_class_ids as $class_id ) {
257 - $post_ids[] = $relations->get_posts_by_style( $class_id );
258 - }
259 -
260 - return array_values( array_unique( array_merge( ...$post_ids ) ) );
84 + return $this->get( $id );
261 85 }
262 86
263 - private function all_from_posts(): Global_Classes {
264 - $order = Global_Classes_Order::make( $this->get_kit() )
265 - ->set_preview( $this->is_preview() )
266 - ->get_order();
87 + public function arrange( array $value ) {
88 + $all = $this->all();
267 89
268 - if ( empty( $order ) ) {
269 - return Global_Classes::make( [], [] );
90 + if ( $all->get_order()->all() === $value ) {
91 + return $value;
270 92 }
271 93
272 - $items = [];
94 + $updated = Plugin::$instance->kits_manager->get_active_kit()->update_json_meta( self::META_KEY, [
95 + 'items' => $all->get_items()->all(),
96 + 'order' => $value,
97 + ] );
273 98
274 - foreach ( $this->iterate_class_posts_for_ids( $order ) as $class_post ) {
275 - $class_data = $class_post->to_array();
276 - $items[ $class_data['id'] ] = $class_data;
99 + if ( ! $updated ) {
100 + throw new \Exception( 'Failed to arrange global classes' );
277 101 }
278 102
279 - $order = Global_Classes_Parser::sanitize_order( $items, $order );
280 -
281 - return Global_Classes::make( $items, $order );
103 + return $this->all()->get_order()->all();
282 104 }
283 105
284 - private function put_to_posts( array $items, array $order, array $current_ids ): void {
285 - $is_preview = $this->is_preview();
286 - $new_ids = array_keys( $items );
106 + private function generate_global_class_id() {
107 + $existing_ids = $this->all()->get_items()->keys();
108 + $kit_id = Plugin::$instance->kits_manager->get_active_kit()->get_id();
287 109
288 - $to_delete = array_diff( $current_ids, $new_ids );
289 - $to_create = array_diff( $new_ids, $current_ids );
290 - $to_update = array_intersect( $new_ids, $current_ids );
291 -
292 - $this->persist_class_batch_mutations( $to_delete, $to_create, $to_update, $items, $is_preview );
293 -
294 - $classes_order = Global_Classes_Order::make( $this->get_kit() )->set_preview( $this->is_preview() );
295 - $classes_order->set_order( $order );
296 -
297 - $label_map = [];
298 - foreach ( $order as $id ) {
299 - if ( isset( $items[ $id ]['label'] ) ) {
300 - $label_map[ $id ] = $items[ $id ]['label'];
301 - }
302 - }
303 - $this->labels()->set_labels( $label_map );
304 -
305 - if ( ! $is_preview ) {
306 - $touched_ids = array_merge( array_values( $to_create ), array_values( $to_update ) );
307 - $touched_items = array_intersect_key(
308 - $items,
309 - array_flip( $touched_ids )
310 - );
311 - Global_Classes_Sync_Map::make( $this->get_kit() )->apply_changes( $touched_items, array_values( $to_delete ) );
312 -
313 - $this->bulk_clear_preview_meta( array_values( $to_update ) );
314 - $this->clear_preview_labels_for_ids( array_merge(
315 - array_values( $to_create ),
316 - array_values( $to_update ),
317 - array_values( $to_delete )
318 - ) );
319 - }
320 - }
321 -
322 - private function iterate_class_posts_for_ids( array $class_ids ): \Generator {
323 - foreach ( array_chunk( $class_ids, self::READ_BATCH_SIZE ) as $chunk ) {
324 - $posts = get_posts( [
325 - 'post_type' => Global_Class_Post_Type::CPT,
326 - 'post_status' => 'publish',
327 - 'posts_per_page' => -1,
328 - 'meta_query' => [
329 - [
330 - 'key' => Global_Class_Post::META_KEY_ID,
331 - 'value' => $chunk,
332 - 'compare' => 'IN',
333 - ],
334 - ],
335 - ] );
336 -
337 - foreach ( $posts as $post ) {
338 - yield Global_Class_Post::from_post( $post, $this->is_preview() );
339 - clean_post_cache( $post->ID );
340 - }
341 - unset( $posts );
342 - $this->flush_runtime_cache();
343 - }
344 - }
345 -
346 - private function persist_class_batch_mutations(
347 - array $to_delete,
348 - array $to_create,
349 - array $to_update,
350 - array $items_by_id,
351 - bool $is_preview
352 - ): void {
353 - $relations = new Global_Classes_Relations();
354 - $post_ids_map = Global_Classes_Post_IDs::make( $this->get_kit() );
355 -
356 - $ids_to_resolve = array_values( array_merge(
357 - array_values( $to_delete ),
358 - array_values( $to_update ),
359 - array_values( $to_create )
360 - ) );
361 - $post_ids = $post_ids_map->get_post_ids( $ids_to_resolve );
362 -
363 - $this->each_class_id_batch(
364 - array_values( $to_delete ),
365 - function ( string $class_id ) use ( $is_preview, $relations, $post_ids ) {
366 - $post = isset( $post_ids[ $class_id ] )
367 - ? Global_Class_Post::from_post_id( $post_ids[ $class_id ], false )
368 - : null;
369 -
370 - if ( ! $post ) {
371 - return;
372 - }
373 -
374 - if ( $is_preview ) {
375 - $post->set_preview( true );
376 - $post->update_data( [] );
377 - clean_post_cache( $post->get_post_id() );
378 - } else {
379 - $relations->clear_class_relations( $class_id );
380 - $post->delete();
381 - }
382 - }
383 - );
384 -
385 - $this->each_class_id_batch( $to_create, function ( string $class_id ) use ( $items_by_id, $is_preview, $post_ids, $post_ids_map ) {
386 - if ( ! isset( $items_by_id[ $class_id ] ) ) {
387 - return;
388 - }
389 -
390 - $item = $items_by_id[ $class_id ];
391 - $data = Global_Class_Data_Normalizer::normalize_style_fields( $item );
392 - $kit = $this->get_kit();
393 - $existing_post_id = $post_ids[ $class_id ] ?? null;
394 - $existing_post = $existing_post_id ? Global_Class_Post::from_post_id( $existing_post_id, $is_preview ) : null;
395 -
396 - if ( $existing_post ) {
397 - $existing_post->update_data( $data );
398 -
399 - if ( ! $is_preview ) {
400 - $existing_post->update_label( $item['label'] );
401 - }
402 -
403 - clean_post_cache( $existing_post->get_post_id() );
404 -
405 - return;
406 - }
407 -
408 - $created = Global_Class_Post::create( $class_id, $item['label'], $data, $kit );
409 -
410 - if ( $created ) {
411 - $post_ids_map->set( $class_id, $created->get_post_id() );
412 - clean_post_cache( $created->get_post_id() );
413 - }
414 - } );
415 -
416 - $this->each_class_id_batch(
417 - $to_update,
418 - function ( string $class_id ) use ( $items_by_id, $is_preview, $post_ids ) {
419 - if ( ! isset( $items_by_id[ $class_id ] ) || ! isset( $post_ids[ $class_id ] ) ) {
420 - return;
421 - }
422 -
423 - $item = $items_by_id[ $class_id ];
424 - $post = Global_Class_Post::from_post_id( $post_ids[ $class_id ], $is_preview );
425 -
426 - if ( ! $post ) {
427 - return;
428 - }
429 -
430 - $data = Global_Class_Data_Normalizer::normalize_style_fields( $item );
431 - $post->update_data( $data );
432 -
433 - if ( ! $is_preview ) {
434 - $post->update_label( $item['label'] );
435 - }
436 -
437 - clean_post_cache( $post->get_post_id() );
438 - }
439 - );
440 - }
441 -
442 - public function delete_all(): void {
443 - $order = $this->get_order();
444 -
445 - $this->each_class_id_batch( $order, function ( string $class_id ) {
446 - $post = Global_Class_Post::find_by_class_id( $class_id );
447 -
448 - if ( $post ) {
449 - $post->delete();
450 - }
451 - } );
452 -
453 - Global_Classes_Order::make( $this->get_kit() )->set_order( [] );
454 - $this->labels()->set_labels( [] );
455 - }
456 -
457 - private function clear_preview_labels_for_ids( array $class_ids ): void {
458 - if ( empty( $class_ids ) ) {
459 - return;
460 - }
461 -
462 - $preview_labels = Global_Classes_Labels::make( $this->get_kit() )->set_preview( true );
463 - $labels_map = $preview_labels->get_labels();
464 -
465 - if ( empty( $labels_map ) ) {
466 - return;
467 - }
468 -
469 - $ids_to_remove = array_intersect( array_unique( $class_ids ), array_keys( $labels_map ) );
470 -
471 - if ( empty( $ids_to_remove ) ) {
472 - return;
473 - }
474 -
475 - foreach ( $ids_to_remove as $id ) {
476 - unset( $labels_map[ $id ] );
477 - }
478 -
479 - $preview_labels->set_labels( $labels_map );
480 - }
481 -
482 - private function bulk_clear_preview_meta( array $class_ids ): void {
483 - if ( empty( $class_ids ) ) {
484 - return;
485 - }
486 -
487 - $post_ids_map = Global_Classes_Post_IDs::make( $this->get_kit() );
488 -
489 - foreach ( array_chunk( $class_ids, self::PERSIST_BATCH_SIZE ) as $chunk ) {
490 - $post_ids = $post_ids_map->get_post_ids( $chunk );
491 -
492 - foreach ( $post_ids as $post_id ) {
493 - delete_post_meta( $post_id, Global_Class_Post::META_KEY_DATA_PREVIEW );
494 - clean_post_cache( $post_id );
495 - }
496 -
497 - $this->flush_runtime_cache();
498 - }
499 - }
500 -
501 - private function each_class_id_batch( $class_ids, callable $callback, int $batch_size = self::PERSIST_BATCH_SIZE ): void {
502 - $class_ids = is_array( $class_ids ) ? $class_ids : iterator_to_array( $class_ids, false );
503 - foreach ( array_chunk( array_values( $class_ids ), $batch_size ) as $batch ) {
504 - foreach ( $batch as $class_id ) {
505 - $callback( $class_id );
506 - }
507 - $this->flush_runtime_cache();
508 - }
509 - }
510 -
511 - private function flush_runtime_cache(): void {
512 - if ( function_exists( 'wp_cache_flush_runtime' ) ) {
513 - wp_cache_flush_runtime();
514 - }
110 + return Atomic_Styles_Utils::generate_id( 'g-' . $kit_id . '-', $existing_ids );
515 111 }
516 112 }