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