PluginProbe
Elementor Website Builder – more than just a page builder / 3.30.3
Elementor Website Builder – more than just a page builder v3.30.3
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 3.30.3, at modules/usage/module.php

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