balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

module.php (15899B)


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