PluginProbe
Elementor Website Builder – more than just a page builder / 4.2.0
Elementor Website Builder – more than just a page builder v4.2.0
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 / usage / module.php

module.php in Elementor Website Builder – more than just a page builder 4.2.0, at modules/usage/module.php

615 lines 15.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\Usage;
3
4 use Elementor\Core\Base\Document;
5 use Elementor\Core\Base\Module as BaseModule;
6 use Elementor\Modules\AtomicWidgets\Logger\Logger;
7 use Elementor\Modules\AtomicWidgets\Module as Atomic_Widgets_Module;
8 use Elementor\Modules\AtomicWidgets\Usage\Atomic_Element_Usage_Calculator;
9 use Elementor\Modules\System_Info\Module as System_Info;
10 use Elementor\Modules\Usage\Calculators\Legacy_Element_Usage_Calculator;
11 use Elementor\Plugin;
12 use Elementor\Settings;
13 use Elementor\Tracker;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Elementor usage module.
21 *
22 * Elementor usage module handler class is responsible for registering and
23 * managing Elementor usage data.
24 */
25 class Module extends BaseModule {
26 const GENERAL_TAB = 'general';
27 const META_KEY = '_elementor_controls_usage';
28 const OPTION_NAME = 'elementor_controls_usage';
29
30 /**
31 * @var bool
32 */
33 private $is_document_saving = false;
34
35 /**
36 * Get module name.
37 *
38 * Retrieve the usage module name.
39 *
40 * @access public
41 *
42 * @return string Module name.
43 */
44 public function get_name() {
45 return 'usage';
46 }
47
48 /**
49 * Get doc type count.
50 *
51 * Get count of documents based on doc type
52 *
53 * Remove 'wp-' from $doc_type for BC, support doc type change since 2.7.0.
54 *
55 * @param \Elementor\Core\Documents_Manager $doc_class
56 * @param String $doc_type
57 *
58 * @return int
59 */
60 public function get_doc_type_count( $doc_class, $doc_type ) {
61 static $posts = null;
62 static $library = null;
63
64 if ( null === $posts ) {
65 $posts = \Elementor\Tracker::get_posts_usage();
66 }
67
68 if ( null === $library ) {
69 $library = \Elementor\Tracker::get_library_usage();
70 }
71
72 $posts_usage = $posts;
73
74 if ( $doc_class::get_property( 'show_in_library' ) ) {
75 $posts_usage = $library;
76 }
77
78 $doc_type_common = str_replace( 'wp-', '', $doc_type );
79
80 $doc_usage = isset( $posts_usage[ $doc_type_common ] ) ? $posts_usage[ $doc_type_common ] : 0;
81
82 return is_array( $doc_usage ) ? $doc_usage['publish'] : $doc_usage;
83 }
84
85 /**
86 * Get formatted usage.
87 *
88 * Retrieve formatted usage, for frontend.
89 *
90 * @param String $format Optional. Default is 'html'.
91 *
92 * @return array
93 */
94 public function get_formatted_usage( $format = 'html' ) {
95 $usage = [];
96
97 foreach ( get_option( self::OPTION_NAME, [] ) as $doc_type => $elements ) {
98 $doc_class = Plugin::$instance->documents->get_document_type( $doc_type );
99
100 if ( 'html' === $format && $doc_class ) {
101 $doc_title = $doc_class::get_title();
102 } else {
103 $doc_title = $doc_type;
104 }
105
106 $doc_count = $this->get_doc_type_count( $doc_class, $doc_type );
107
108 $tab_group = $doc_class::get_property( 'admin_tab_group' );
109
110 if ( 'html' === $format && $tab_group ) {
111 $doc_title = ucwords( $tab_group ) . ' - ' . $doc_title;
112 }
113
114 // Replace element type with element title.
115 foreach ( $elements as $element_type => $data ) {
116 unset( $elements[ $element_type ] );
117
118 if ( in_array( $element_type, [ 'section', 'column' ], true ) ) {
119 continue;
120 }
121
122 $widget_instance = Plugin::$instance->widgets_manager->get_widget_types( $element_type );
123
124 if ( 'html' === $format && $widget_instance ) {
125 $widget_title = $widget_instance->get_title();
126 } else {
127 $widget_title = $element_type;
128 }
129
130 $widget_title = apply_filters( 'elementor/usage/elements/element_title', $widget_title, $element_type );
131
132 $elements[ $widget_title ] = $data['count'];
133 }
134
135 // Sort elements by key.
136 ksort( $elements );
137
138 $usage[ $doc_type ] = [
139 'title' => $doc_title,
140 'elements' => $elements,
141 'count' => $doc_count,
142 ];
143
144 // ' ? 1 : 0;' In sorters is compatibility for PHP8.0.
145 // Sort usage by title.
146 uasort( $usage, function( $a, $b ) {
147 return ( $a['title'] > $b['title'] ) ? 1 : 0;
148 } );
149
150 // If title includes '-' will have lower priority.
151 uasort( $usage, function( $a ) {
152 return strpos( $a['title'], '-' ) ? 1 : 0;
153 } );
154 }
155
156 return $usage;
157 }
158
159 /**
160 * Before document Save.
161 *
162 * Called on elementor/document/before_save, remove document from global & set saving flag.
163 *
164 * @param Document $document
165 * @param array $data new settings to save.
166 */
167 public function before_document_save( $document, $data ) {
168 $current_status = get_post_status( $document->get_post() );
169 $new_status = isset( $data['settings']['post_status'] ) ? $data['settings']['post_status'] : '';
170
171 if ( $current_status === $new_status ) {
172 $this->remove_from_global( $document );
173 }
174
175 $this->is_document_saving = true;
176 }
177
178 /**
179 * After document save.
180 *
181 * Called on elementor/document/after_save, adds document to global & clear saving flag.
182 *
183 * @param Document $document
184 */
185 public function after_document_save( $document ) {
186 if ( Document::STATUS_PUBLISH === $document->get_post()->post_status || Document::STATUS_PRIVATE === $document->get_post()->post_status ) {
187 $this->save_document_usage( $document );
188 }
189
190 $this->is_document_saving = false;
191 }
192
193 /**
194 * On status change.
195 *
196 * Called on transition_post_status.
197 *
198 * @param string $new_status
199 * @param string $old_status
200 * @param \WP_Post $post
201 */
202 public function on_status_change( $new_status, $old_status, $post ) {
203 if ( wp_is_post_autosave( $post ) ) {
204 return;
205 }
206
207 // If it's from elementor editor, the usage should be saved via `before_document_save`/`after_document_save`.
208 if ( $this->is_document_saving ) {
209 return;
210 }
211
212 $document = Plugin::$instance->documents->get( $post->ID );
213
214 if ( ! $document ) {
215 return;
216 }
217
218 $is_public_unpublish = 'publish' === $old_status && 'publish' !== $new_status;
219 $is_private_unpublish = 'private' === $old_status && 'private' !== $new_status;
220
221 if ( $is_public_unpublish || $is_private_unpublish ) {
222 $this->remove_from_global( $document );
223 }
224
225 $is_public_publish = 'publish' !== $old_status && 'publish' === $new_status;
226 $is_private_publish = 'private' !== $old_status && 'private' === $new_status;
227
228 if ( $is_public_publish || $is_private_publish ) {
229 $this->save_document_usage( $document );
230 }
231 }
232
233 /**
234 * On before delete post.
235 *
236 * Called on on_before_delete_post.
237 *
238 * @param int $post_id
239 */
240 public function on_before_delete_post( $post_id ) {
241 $document = Plugin::$instance->documents->get( $post_id );
242
243 if ( $document->get_id() !== $document->get_main_id() ) {
244 return;
245 }
246
247 $this->remove_from_global( $document );
248 }
249
250 /**
251 * Add's tracking data.
252 *
253 * Called on elementor/tracker/send_tracking_data_params.
254 *
255 * @param array $params
256 *
257 * @return array
258 */
259 public function add_tracking_data( $params ) {
260 $params['usages']['elements'] = get_option( self::OPTION_NAME );
261
262 return $params;
263 }
264
265 /**
266 * Recalculate usage.
267 *
268 * Recalculate usage for all elementor posts.
269 *
270 * @param int $limit
271 * @param int $offset
272 *
273 * @return int
274 */
275 public function recalc_usage( $limit = -1, $offset = 0 ) {
276 // While requesting recalc_usage, data should be deleted.
277 // if its in a batch the data should be deleted only on the first batch.
278 if ( 0 === $offset ) {
279 delete_option( self::OPTION_NAME );
280 }
281
282 $post_types = get_post_types( [ 'public' => true ] );
283
284 $query = new \WP_Query( [
285 'no_found_rows' => true,
286 'meta_key' => '_elementor_data',
287 'post_type' => $post_types,
288 'post_status' => [ 'publish', 'private' ],
289 'posts_per_page' => $limit,
290 'offset' => $offset,
291 ] );
292
293 foreach ( $query->posts as $post ) {
294 $document = Plugin::$instance->documents->get( $post->ID );
295
296 if ( ! $document ) {
297 continue;
298 }
299
300 $this->after_document_save( $document );
301 }
302
303 // Clear query memory before leave.
304 wp_cache_flush();
305
306 return count( $query->posts );
307 }
308
309 /**
310 * Increase controls count.
311 *
312 * Increase controls count, for each element.
313 *
314 * @param array &$element_ref
315 * @param string $tab
316 * @param string $section
317 * @param string $control
318 * @param int $count
319 */
320 private function increase_controls_count( &$element_ref, $tab, $section, $control, $count ) {
321 if ( ! isset( $element_ref['controls'][ $tab ] ) ) {
322 $element_ref['controls'][ $tab ] = [];
323 }
324
325 if ( ! isset( $element_ref['controls'][ $tab ][ $section ] ) ) {
326 $element_ref['controls'][ $tab ][ $section ] = [];
327 }
328
329 if ( ! isset( $element_ref['controls'][ $tab ][ $section ][ $control ] ) ) {
330 $element_ref['controls'][ $tab ][ $section ][ $control ] = 0;
331 }
332
333 $element_ref['controls'][ $tab ][ $section ][ $control ] += $count;
334 }
335
336 /**
337 * Add to global.
338 *
339 * Add's usage to global (update database).
340 *
341 * @param string $doc_name
342 * @param array $doc_usage
343 */
344 private function add_to_global( $doc_name, $doc_usage ) {
345 $global_usage = get_option( self::OPTION_NAME, [] );
346
347 foreach ( $doc_usage as $element_type => $element_data ) {
348 if ( ! isset( $global_usage[ $doc_name ] ) ) {
349 $global_usage[ $doc_name ] = [];
350 }
351
352 if ( ! isset( $global_usage[ $doc_name ][ $element_type ] ) ) {
353 $global_usage[ $doc_name ][ $element_type ] = [
354 'count' => 0,
355 'controls' => [],
356 ];
357 }
358
359 $global_element_ref = &$global_usage[ $doc_name ][ $element_type ];
360 $global_element_ref['count'] += $element_data['count'];
361
362 if ( empty( $element_data['controls'] ) ) {
363 continue;
364 }
365
366 foreach ( $element_data['controls'] as $tab => $sections ) {
367 foreach ( $sections as $section => $controls ) {
368 foreach ( $controls as $control => $count ) {
369 $this->increase_controls_count( $global_element_ref, $tab, $section, $control, $count );
370 }
371 }
372 }
373 }
374
375 update_option( self::OPTION_NAME, $global_usage, false );
376 }
377
378 /**
379 * Remove from global.
380 *
381 * Remove's usage from global (update database).
382 *
383 * @param Document $document
384 */
385 private function remove_from_global( $document ) {
386 $prev_usage = $document->get_meta( self::META_KEY );
387
388 if ( empty( $prev_usage ) ) {
389 return;
390 }
391
392 $doc_name = $document->get_name();
393
394 $global_usage = get_option( self::OPTION_NAME, [] );
395
396 foreach ( $prev_usage as $element_type => $doc_value ) {
397 if ( isset( $global_usage[ $doc_name ][ $element_type ]['count'] ) ) {
398 $global_usage[ $doc_name ][ $element_type ]['count'] -= $prev_usage[ $element_type ]['count'];
399
400 if ( 0 === $global_usage[ $doc_name ][ $element_type ]['count'] ) {
401 unset( $global_usage[ $doc_name ][ $element_type ] );
402
403 if ( 0 === count( $global_usage[ $doc_name ] ) ) {
404 unset( $global_usage[ $doc_name ] );
405 }
406
407 continue;
408 }
409
410 foreach ( $prev_usage[ $element_type ]['controls'] as $tab => $sections ) {
411 foreach ( $sections as $section => $controls ) {
412 foreach ( $controls as $control => $count ) {
413 if ( isset( $global_usage[ $doc_name ][ $element_type ]['controls'][ $tab ][ $section ][ $control ] ) ) {
414 $section_ref = &$global_usage[ $doc_name ][ $element_type ]['controls'][ $tab ][ $section ];
415
416 $section_ref[ $control ] -= $count;
417
418 if ( 0 === $section_ref[ $control ] ) {
419 unset( $section_ref[ $control ] );
420 }
421 }
422 }
423 }
424 }
425 }
426 }
427
428 update_option( self::OPTION_NAME, $global_usage, false );
429
430 $document->delete_meta( self::META_KEY );
431 }
432
433 /**
434 * Get elements usage.
435 *
436 * Get's the current elements usage by passed elements array parameter.
437 *
438 * @param array $elements
439 *
440 * @return array
441 */
442 private function get_elements_usage( $elements ) {
443 $usage = [];
444 $registry = $this->get_calculator_registry();
445
446 Plugin::$instance->db->iterate_data( $elements, function ( $element ) use ( &$usage, $registry ) {
447 if ( empty( $element['widgetType'] ) ) {
448 $type = $element['elType'];
449 $element_instance = Plugin::$instance->elements_manager->get_element_types( $type );
450 } else {
451 $type = $element['widgetType'];
452 $element_instance = Plugin::$instance->widgets_manager->get_widget_types( $type );
453 }
454
455 try {
456 $calculator = $registry->get_calculator_for( $element, $element_instance );
457
458 if ( $calculator ) {
459 $usage = $calculator->calculate( $element, $element_instance, $usage );
460 }
461 } catch ( \Throwable $e ) {
462 Logger::warning(
463 'Usage calculation failed: ' . $e->getMessage(),
464 [
465 'element_type' => $type,
466 'element_id' => $element['id'] ?? 'unknown',
467 ]
468 );
469
470 if ( ! isset( $usage[ $type ] ) ) {
471 $usage[ $type ] = [
472 'count' => 0,
473 'control_percent' => 0,
474 'controls' => [],
475 ];
476 }
477 $usage[ $type ]['count']++;
478 }
479
480 return $element;
481 } );
482
483 return $usage;
484 }
485
486 /**
487 * @return Element_Usage_Calculator_Registry
488 */
489 private function get_calculator_registry(): Element_Usage_Calculator_Registry {
490 static $registry = null;
491
492 if ( null === $registry ) {
493 $calculators = [];
494
495 if ( Atomic_Widgets_Module::is_active() ) {
496 $calculators[] = new Atomic_Element_Usage_Calculator();
497 }
498
499 $registry = new Element_Usage_Calculator_Registry( $calculators );
500 $registry->set_fallback( new Legacy_Element_Usage_Calculator() );
501 }
502
503 return $registry;
504 }
505
506 /**
507 * Save document usage.
508 *
509 * Save requested document usage, and update global.
510 *
511 * @param Document $document
512 */
513 private function save_document_usage( Document $document ) {
514 if ( ! $document::get_property( 'is_editable' ) && ! $document->is_built_with_elementor() ) {
515 return;
516 }
517
518 // Get data manually to avoid conflict with `\Elementor\Core\Base\Document::get_elements_data... convert_to_elementor`.
519 $data = $document->get_json_meta( '_elementor_data' );
520
521 if ( ! empty( $data ) ) {
522 try {
523 $usage = $this->get_elements_usage( $document->get_elements_raw_data( $data ) );
524
525 $document->update_meta( self::META_KEY, $usage );
526
527 $this->add_to_global( $document->get_name(), $usage );
528 } catch ( \Exception $exception ) {
529 Logger::warning( $exception->getMessage(), [
530 'document_id' => $document->get_id(),
531 'document_name' => $document->get_name(),
532 ] );
533
534 return;
535 }
536 }
537 }
538
539 public static function get_settings_usage() {
540 $usage = [];
541
542 $settings_tab = Plugin::$instance->settings->get_tabs();
543 $settings = array_merge(
544 $settings_tab[ Settings::TAB_GENERAL ]['sections'],
545 $settings_tab[ Settings::TAB_ADVANCED ]['sections']
546 );
547
548 foreach ( $settings as $setting_data ) {
549 foreach ( $setting_data['fields'] as $field_name => $field_data ) {
550 $is_hidden_field = ( empty( $field_data['field_args']['type'] ) || 'hidden' === $field_data['field_args']['type'] );
551
552 if ( $is_hidden_field ) {
553 continue;
554 }
555
556 $setting_value = get_option( 'elementor_' . $field_name );
557
558 if ( empty( $setting_value ) ) {
559 continue;
560 }
561
562 $is_default_value = ( ! empty( $field_data['field_args']['std'] ) && $setting_value === $field_data['field_args']['std'] );
563
564 if ( $is_default_value ) {
565 continue;
566 }
567
568 $usage[ $field_name ] = $setting_value;
569 }
570 }
571
572 $usage = apply_filters( 'elementor/system-info/usage/settings', $usage );
573
574 return $usage;
575 }
576
577 /**
578 * Add system info report.
579 */
580 public function add_system_info_report() {
581 System_Info::add_report( 'usage', [
582 'file_name' => __DIR__ . '/usage-reporter.php',
583 'class_name' => __NAMESPACE__ . '\Usage_Reporter',
584 ] );
585
586 System_Info::add_report( 'settings', [
587 'file_name' => __DIR__ . '/settings-reporter.php',
588 'class_name' => __NAMESPACE__ . '\Settings_Reporter',
589 ] );
590 }
591
592 /**
593 * Usage module constructor.
594 *
595 * Initializing Elementor usage module.
596 *
597 * @access public
598 */
599 public function __construct() {
600 if ( ! Tracker::is_allow_track() ) {
601 return;
602 }
603
604 add_action( 'transition_post_status', [ $this, 'on_status_change' ], 10, 3 );
605 add_action( 'before_delete_post', [ $this, 'on_before_delete_post' ] );
606
607 add_action( 'elementor/document/before_save', [ $this, 'before_document_save' ], 10, 2 );
608 add_action( 'elementor/document/after_save', [ $this, 'after_document_save' ] );
609
610 add_filter( 'elementor/tracker/send_tracking_data_params', [ $this, 'add_tracking_data' ] );
611
612 add_action( 'admin_init', [ $this, 'add_system_info_report' ], 50 );
613 }
614 }
615