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