PluginProbe
Elementor Website Builder – more than just a page builder / 4.2.2
Elementor Website Builder – more than just a page builder v4.2.2
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 4.0.7 All 451 releases
elementor / modules / global-classes / global-classes-relations.php

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

367 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\GlobalClasses;
4
5 use Elementor\Core\Base\Document;
6 use Elementor\Modules\GlobalClasses\Atomic_Global_Styles;
7 use Elementor\Modules\GlobalClasses\Concerns\Has_Preview_Context;
8 use Elementor\Modules\GlobalClasses\Utils\Atomic_Elements_Utils;
9 use Elementor\Plugin;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit; // Exit if accessed directly.
13 }
14
15 class Global_Classes_Relations {
16 use Has_Preview_Context;
17
18 const META_KEY_FRONTEND = '_elementor_used_global_class';
19 const META_KEY_PREVIEW = '_elementor_used_global_class_preview';
20
21 const META_KEY_USAGE_INDEXED_FRONTEND = '_elementor_global_class_usage_indexed';
22 const META_KEY_USAGE_INDEXED_PREVIEW = '_elementor_global_class_usage_indexed_preview';
23
24 const META_KEY_CLASS_RELATED_POSTS_FRONTEND = '_elementor_global_class_using_documents';
25 const META_KEY_CLASS_RELATED_POSTS_PREVIEW = '_elementor_global_class_using_documents_preview';
26
27 protected array $context_keys = [
28 'used_classes' => [
29 'frontend' => self::META_KEY_FRONTEND,
30 'preview' => self::META_KEY_PREVIEW,
31 ],
32 'usage_indexed' => [
33 'frontend' => self::META_KEY_USAGE_INDEXED_FRONTEND,
34 'preview' => self::META_KEY_USAGE_INDEXED_PREVIEW,
35 ],
36 'related_posts' => [
37 'frontend' => self::META_KEY_CLASS_RELATED_POSTS_FRONTEND,
38 'preview' => self::META_KEY_CLASS_RELATED_POSTS_PREVIEW,
39 ],
40 ];
41
42 public function register_hooks(): void {
43 add_action(
44 'elementor/document/after_save',
45 fn( Document $document ) => $this->on_document_save( $document )
46 );
47 }
48
49 public function collect_class_ids_from_post( int $post_id, ?array $restrict_to_ids = null ): array {
50 $ids = $this->extract_class_ids_from_post( $post_id );
51
52 if ( null === $restrict_to_ids ) {
53 return $ids;
54 }
55
56 return array_values( array_intersect( $ids, $restrict_to_ids ) );
57 }
58
59 public function get_posts_by_style( string $style_id ): array {
60 $from_index = $this->get_posts_from_reverse_index( $style_id );
61
62 if ( ! empty( $from_index ) ) {
63 return $from_index;
64 }
65
66 $post_ids = get_posts( [
67 'fields' => 'ids',
68 'meta_query' => [
69 [
70 'key' => $this->get_context_key( 'used_classes' ),
71 'value' => $style_id,
72 'compare' => '=',
73 ],
74 ],
75 'no_found_rows' => true,
76 'post_status' => 'any',
77 'post_type' => 'any',
78 'posts_per_page' => -1,
79 'update_post_meta_cache' => false,
80 ] );
81
82 return array_map( 'intval', $post_ids );
83 }
84
85 public function get_styles_by_post( int $post_id ): array {
86 $stored_ids = $this->get_stored_style_ids( $post_id );
87 $live_ids = array_values( array_unique( $this->extract_class_ids_from_post( $post_id ) ) );
88 $has_elementor_data = $this->document_has_elementor_data( $post_id );
89
90 $normalize = static function ( array $ids ): string {
91 $ids = array_values( array_unique( $ids ) );
92 sort( $ids );
93
94 return wp_json_encode( $ids );
95 };
96
97 if ( ! $has_elementor_data ) {
98 if ( ! empty( $stored_ids ) ) {
99 $this->mark_usage_indexed( $post_id );
100
101 return $stored_ids;
102 }
103
104 if ( $this->is_usage_indexed( $post_id ) ) {
105 return [];
106 }
107
108 $this->mark_usage_indexed( $post_id );
109
110 return [];
111 }
112
113 if ( $normalize( $stored_ids ) !== $normalize( $live_ids ) ) {
114 $this->set_styles_for_post( $post_id, $live_ids );
115
116 return $live_ids;
117 }
118
119 if ( ! empty( $live_ids ) ) {
120 $this->mark_usage_indexed( $post_id );
121 }
122
123 return $live_ids;
124 }
125
126 public function clear_class_relations( string $class_id ): void {
127 $post_ids = $this->get_posts_by_style( $class_id );
128
129 foreach ( $post_ids as $post_id ) {
130 $stored_ids = $this->get_stored_style_ids( $post_id );
131 $updated_ids = array_values( array_filter( $stored_ids, fn( $id ) => $id !== $class_id ) );
132 $this->replace_stored_style_ids( $post_id, $updated_ids );
133 }
134 }
135
136 public function clear_post_styles( int $post_id ): void {
137 $saved_preview = $this->is_preview();
138
139 try {
140 foreach ( [ false, true ] as $preview_flag ) {
141 $this->set_preview( $preview_flag );
142 $old_ids = $this->get_stored_style_ids( $post_id );
143
144 delete_post_meta( $post_id, $this->get_context_key( 'used_classes' ) );
145 delete_post_meta( $post_id, $this->get_context_key( 'usage_indexed' ) );
146
147 foreach ( $old_ids as $class_id ) {
148 $this->unlink_post_from_class( $class_id, $post_id );
149 }
150 }
151 } finally {
152 $this->set_preview( $saved_preview );
153 }
154 }
155
156 public function set_styles_for_post( int $post_id, array $style_ids ): void {
157 $old_ids = $this->get_stored_style_ids( $post_id );
158
159 foreach ( array_diff( $old_ids, $style_ids ) as $class_id ) {
160 $this->unlink_post_from_class( $class_id, $post_id );
161 }
162
163 $this->replace_stored_style_ids( $post_id, $style_ids );
164
165 foreach ( array_diff( $style_ids, $old_ids ) as $class_id ) {
166 $this->link_post_to_class( $class_id, $post_id );
167 }
168
169 $this->mark_usage_indexed( $post_id );
170 }
171
172 private function get_posts_from_reverse_index( string $class_id ): array {
173 $ids = $this->read_reverse_index_for_class( $class_id );
174
175 if (
176 ! empty( $ids )
177 || Global_Classes_Repository::CONTEXT_PREVIEW !== $this->get_context_key( 'used_classes' )
178 ) {
179 return $ids;
180 }
181
182 return ( new self() )->read_reverse_index_for_class( $class_id );
183 }
184
185 private function read_reverse_index_for_class( string $class_id ): array {
186 $post = Global_Class_Post::find_by_class_id( $class_id );
187
188 if ( ! $post ) {
189 return [];
190 }
191
192 $ids = get_post_meta( $post->get_post_id(), $this->get_context_key( 'related_posts' ), true );
193
194 if ( ! is_array( $ids ) ) {
195 return [];
196 }
197
198 return array_values( array_unique( array_map( 'intval', $ids ) ) );
199 }
200
201 private function link_post_to_class( string $class_id, int $document_post_id ): void {
202 $post = Global_Class_Post::find_by_class_id( $class_id );
203
204 if ( ! $post ) {
205 return;
206 }
207
208 $cpt_id = $post->get_post_id();
209 $ids = get_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), true );
210 $ids = is_array( $ids ) ? array_map( 'intval', $ids ) : [];
211
212 if ( in_array( $document_post_id, $ids, true ) ) {
213 return;
214 }
215
216 $ids[] = $document_post_id;
217
218 update_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), $ids );
219 }
220
221 private function unlink_post_from_class( string $class_id, int $document_post_id ): void {
222 $post = Global_Class_Post::find_by_class_id( $class_id );
223
224 if ( ! $post ) {
225 return;
226 }
227
228 $cpt_id = $post->get_post_id();
229 $ids = get_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), true );
230
231 if ( ! is_array( $ids ) ) {
232 return;
233 }
234
235 $ids = array_values(
236 array_filter(
237 array_map( 'intval', $ids ),
238 fn( $id ) => $id !== $document_post_id
239 )
240 );
241
242 update_post_meta( $cpt_id, $this->get_context_key( 'related_posts' ), $ids );
243 }
244
245 private function on_document_save( Document $document ): void {
246 $post_id = $document->get_main_id();
247
248 static $in_progress = [];
249 if ( isset( $in_progress[ $post_id ] ) ) {
250 return;
251 }
252 $in_progress[ $post_id ] = true;
253
254 $saved_preview = $this->is_preview();
255
256 try {
257 $this->invalidate_document_styles_cache( $post_id );
258
259 $this->set_preview( false );
260 $this->set_styles_for_post( $post_id, $this->extract_class_ids_from_post( $post_id ) );
261
262 $this->set_preview( true );
263 $this->set_styles_for_post( $post_id, $this->extract_class_ids_from_post( $post_id ) );
264 } finally {
265 $this->set_preview( $saved_preview );
266 unset( $in_progress[ $post_id ] );
267 }
268 }
269
270 private function extract_class_ids_from_post( int $post_id ): array {
271 $used_class_ids = [];
272
273 $document = $this->get_document_for_post( $post_id );
274
275 if ( ! $document ) {
276 return [];
277 }
278
279 $elements_data = $document->get_elements_data();
280
281 if ( empty( $elements_data ) ) {
282 return [];
283 }
284
285 Plugin::$instance->db->iterate_data( $elements_data, function ( $element_data ) use ( &$used_class_ids ) {
286 $used_class_ids = array_merge(
287 $used_class_ids,
288 Atomic_Elements_Utils::collect_class_ids_from_element_data( $element_data )
289 );
290 } );
291
292 return array_values( array_unique( $used_class_ids ) );
293 }
294
295 private function get_stored_style_ids( int $post_id ): array {
296 $meta_values = get_post_meta( $post_id, $this->get_context_key( 'used_classes' ), false );
297
298 if ( ! is_array( $meta_values ) ) {
299 return [];
300 }
301
302 return array_values( array_unique( $meta_values ) );
303 }
304
305 private function replace_stored_style_ids( int $post_id, array $style_ids ): void {
306 delete_post_meta( $post_id, $this->get_context_key( 'used_classes' ) );
307
308 $unique_ids = array_unique( $style_ids );
309
310 foreach ( $unique_ids as $class_id ) {
311 add_post_meta( $post_id, $this->get_context_key( 'used_classes' ), $class_id );
312 }
313 }
314
315 private function is_usage_indexed( int $post_id ): bool {
316 return '1' === get_post_meta( $post_id, $this->get_context_key( 'usage_indexed' ), true );
317 }
318
319 private function mark_usage_indexed( int $post_id ): void {
320 update_post_meta( $post_id, $this->get_context_key( 'usage_indexed' ), '1' );
321 }
322
323 private function invalidate_document_styles_cache( int $post_id ): void {
324 do_action( 'elementor/atomic-widgets/styles/clear', [ Atomic_Global_Styles::STYLES_KEY, $post_id ] );
325 do_action(
326 'elementor/atomic-widgets/styles/clear',
327 [ Atomic_Global_Styles::STYLES_KEY, $post_id, Global_Classes_Repository::CONTEXT_PREVIEW ]
328 );
329 }
330
331 private function document_has_elementor_data( int $post_id ): bool {
332 $document = $this->get_document_for_post( $post_id );
333
334 if ( ! $document ) {
335 return false;
336 }
337
338 $elements_data = $document->get_elements_data();
339
340 return ! empty( $elements_data );
341 }
342
343 private function get_document_for_post( int $post_id ): ?Document {
344 $documents = Plugin::$instance->documents;
345
346 if ( ! $this->is_preview() ) {
347 return $this->get_document_or_null( $documents->get( $post_id ) );
348 }
349
350 $document = $documents->get_doc_or_auto_save( $post_id, get_current_user_id() );
351
352 if ( ! $document ) {
353 $document = $documents->get( $post_id );
354 }
355
356 return $this->get_document_or_null( $document );
357 }
358
359 private function get_document_or_null( $document ): ?Document {
360 if ( empty( $document ) ) {
361 return null;
362 }
363
364 return $document;
365 }
366 }
367