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