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.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 4.1.0-dev2 4.0.8 4.1.0-beta1 All 453 releases
elementor / modules / global-classes / global-classes-repository.php

global-classes-repository.php in Elementor Website Builder – more than just a page builder 4.1.0-beta1, at modules/global-classes/global-classes-repository.php

413 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Modules\GlobalClasses;
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
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly.
11 }
12
13 class Global_Classes_Repository {
14 use Has_Kit_Dependency;
15 use Has_Preview_Context;
16
17 const META_KEY_FRONTEND = '_elementor_global_classes';
18 const META_KEY_PREVIEW = '_elementor_global_classes_preview';
19
20 const CONTEXT_FRONTEND = 'frontend';
21 const CONTEXT_PREVIEW = 'preview';
22
23 const READ_BATCH_SIZE = 100;
24 const PERSIST_BATCH_SIZE = 100;
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
37 private ?Global_Classes $cache = null;
38
39 public function __construct( ?Kit $kit = null ) {
40 if ( null !== $kit ) {
41 $this->set_kit( $kit );
42 }
43 }
44
45 public static function make( ?Kit $kit = null ): Global_Classes_Repository {
46 return new self( $kit );
47 }
48
49 protected function on_preview_change(): void {
50 $this->cache = null;
51 }
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 */
58 public function all( bool $force = false ): Global_Classes {
59 if ( ! $force && null !== $this->cache ) {
60 return $this->cache;
61 }
62
63 $this->cache = $this->all_from_posts();
64
65 return $this->cache;
66 }
67
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;
84 }
85
86 $labels->set_labels( $existing_labels );
87
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 ];
131 }
132 }
133
134 $this->persist_class_batch_mutations( $to_delete, $to_create, $to_update, $touched_items, $is_preview );
135
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 ] );
161 }
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
177 public function put( array $items, array $order ) {
178 $current_ids = Global_Classes_Order::make( $this->get_kit() )->get_order();
179
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,
189 ];
190
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( [], [] );
204 }
205
206 $items = [];
207
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 }
212
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 }
236 }
237 $this->labels()->set_labels( $label_map );
238
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 } );
254 }
255 }
256
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 ] );
271
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 }
294 }
295
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 } );
367 }
368
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;
411 }
412 }
413