File "class-product-analytics-controller.php"

Full Path: /home/digimqhe/flashdigi.uk/comment-content/sections/modules/class-product-analytics-controller.php
File size: 39.28 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace Smush\Core\Modules;

use Smush\Core\Array_Utils;
use Smush\Core\CDN\CDN_Helper;
use Smush\Core\Helper;
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Media\Media_Item_Query;
use Smush\Core\Media_Library\Background_Media_Library_Scanner;
use Smush\Core\Media_Library\Media_Library_Last_Process;
use Smush\Core\Media_Library\Media_Library_Scan_Background_Process;
use Smush\Core\Media_Library\Media_Library_Scanner;
use Smush\Core\Modules\Background\Background_Pre_Flight_Controller;
use Smush\Core\Modules\Background\Background_Process;
use Smush\Core\Next_Gen\Next_Gen_Manager;
use Smush\Core\Product_Analytics;
use Smush\Core\Server_Utils;
use Smush\Core\Settings;
use Smush\Core\Stats\Global_Stats;
use Smush\Core\Webp\Webp_Configuration;
use WP_Smush;

class Product_Analytics_Controller {
	/**
	 * @var Settings
	 */
	private $settings;
	/**
	 * @var Media_Library_Scan_Background_Process
	 */
	private $scan_background_process;
	private $scanner_slice_size;

	/**
	 * @var Media_Library_Last_Process
	 */
	private $media_library_last_process;

	/**
	 * @var bool
	 */
	private $scan_background_process_dead = false;
	/**
	 * @var Product_Analytics
	 */
	private $product_analytics;

	/**
	 * @var Next_Gen_Manager
	 */
	private $next_gen_manager;

	public function __construct() {
		$this->settings                   = Settings::get_instance();
		$this->scan_background_process    = Background_Media_Library_Scanner::get_instance()->get_background_process();
		$this->media_library_last_process = Media_Library_Last_Process::get_instance();
		$this->product_analytics          = Product_Analytics::get_instance();
		$this->next_gen_manager           = Next_Gen_Manager::get_instance();

		$this->hook_actions();
	}

	private function hook_actions() {
		// Setting events.
		add_action( 'wp_smush_settings_updated', array( $this, 'track_opt_toggle' ), 10, 2 );
		add_action( 'wp_smush_settings_updated', array( $this, 'intercept_settings_update' ), 10, 2 );
		add_action( 'wp_smush_settings_deleted', array( $this, 'intercept_reset' ) );
		add_action( 'wp_smush_settings_updated', array( $this, 'track_integrations_saved' ), 10, 2 );
		add_action( 'wp_smush_settings_updated', array( $this, 'track_toggle_next_gen_fallback' ), 10, 2 );

		add_action( 'wp_ajax_smush_track_deactivate', array( $this, 'ajax_track_deactivation_survey' ) );
		add_action( 'wp_ajax_smush_analytics_track_event', array( $this, 'ajax_handle_track_request' ) );

		if ( ! $this->settings->get( 'usage' ) ) {
			return;
		}

		// Other events.
		add_action( 'wp_smush_directory_smush_start', array( $this, 'track_directory_smush' ) );
		add_action( 'wp_smush_bulk_smush_start', array( $this, 'track_bulk_smush_start' ), 20 );
		add_action( 'wp_smush_bulk_smush_completed', array( $this, 'track_background_bulk_smush_completed' ) );
		add_action( 'wp_smush_bulk_smush_dead', array( $this, 'track_bulk_smush_background_process_death' ) );
		add_action( 'wp_smush_config_applied', array( $this, 'track_config_applied' ) );
		add_action( 'wp_smush_webp_method_changed', array( $this, 'track_webp_method_changed' ) );
		add_action( 'wp_smush_webp_status_changed', array( $this, 'track_next_gen_status_changed' ) );
		add_action( 'wp_smush_avif_status_changed', array( $this, 'track_next_gen_status_changed' ) );
		add_action( 'wp_smush_after_delete_all_webp_files', array(
			$this,
			'track_deleting_all_next_gen_files',
		) );
		add_action( 'wp_smush_after_delete_all_avif_files', array( $this,
			'track_deleting_all_next_gen_files',
		) );
		add_action( 'wp_ajax_smush_toggle_webp_wizard', array( $this, 'track_webp_reconfig' ), - 1 );
		add_action( 'shutdown', array( $this, 'maybe_track_next_gen_format_changed' ) );

		$identifier          = $this->scan_background_process->get_identifier();
		$scan_started_action = "{$identifier}_started";
		$scan_dead_action    = "{$identifier}_dead";

		add_action( "{$identifier}_before_start", array( $this, 'record_scan_death' ), 10, 2 );
		add_action( $scan_started_action, array( $this, 'track_background_scan_start' ), 10, 2 );
		add_action( "{$identifier}_completed", array( $this, 'track_background_scan_end' ), 10, 2 );

		add_action( $scan_dead_action, array( $this, 'track_background_scan_process_death' ) );

		add_action( 'wp_smush_plugin_activated', array( $this, 'track_plugin_activation' ) );
		if ( defined( 'WP_SMUSH_BASENAME' ) ) {
			$plugin_basename = WP_SMUSH_BASENAME;
			add_action( "deactivate_$plugin_basename", array( $this, 'track_plugin_deactivation' ) );
		}

		add_action( 'wp_smush_bulk_smush_stuck', array( $this, 'track_bulk_smush_progress_stuck' ) );

		add_action( 'wp_smush_lazy_load_updated', array( $this, 'track_lazy_load_settings_updated' ), 10, 2 );
	}

	private function track( $event, $properties = array() ) {
		$this->product_analytics->track( $event, $properties );
	}

	public function intercept_settings_update( $old_settings, $settings ) {
		if ( empty( $settings['usage'] ) ) {
			// Use the most up-to-data value of 'usage'
			return;
		}

		$settings = $this->remove_unchanged_settings( $old_settings, $settings );
		$handled  = $this->maybe_track_feature_toggle( $settings );

		if ( ! $handled ) {
			$this->maybe_track_cdn_update( $settings );
		}
	}

	private function maybe_track_feature_toggle( array $settings ) {
		foreach ( $settings as $setting_key => $setting_value ) {
			$handler = "track_{$setting_key}_feature_toggle";
			if ( method_exists( $this, $handler ) ) {
				call_user_func( array( $this, $handler ), $setting_value );

				return true;
			}
		}

		return false;
	}

	private function remove_unchanged_settings( $old_settings, $settings ) {
		$changed = array();
		foreach ( $settings as $setting_key => $setting_value ) {
			$old_setting_value = isset( $old_settings[ $setting_key ] ) ? $old_settings[ $setting_key ] : '';
			$setting_value     = isset( $setting_value ) ? $setting_value : '';
			if ( $old_setting_value !== $setting_value ) {
				$changed[ $setting_key ] = $setting_value;
			}
		}

		return $changed;
	}

	public function get_bulk_properties() {
		$bulk_property_labels = array(
			'auto'       => 'Automatic Compression',
			'strip_exif' => 'Metadata',
			'resize'     => 'Resize Original Images',
			'original'   => 'Compress original images',
			'backup'     => 'Backup original images',
			'png_to_jpg' => 'Auto-convert PNGs to JPEGs (lossy)',
			'no_scale'   => 'Disable scaled images',
		);

		$image_sizes     = Settings::get_instance()->get_setting( 'wp-smush-image_sizes' );
		$bulk_properties = array(
			'Image Sizes'         => empty( $image_sizes ) ? 'All' : 'Custom',
			'Mode'                => $this->get_current_lossy_level_label(),
			'Parallel Processing' => $this->get_parallel_processing_status(),
			'Smush Type'          => $this->get_smush_type(),
		);

		foreach ( $bulk_property_labels as $bulk_setting => $bulk_property_label ) {
			$property_value                          = Settings::get_instance()->get( $bulk_setting )
				? 'Enabled'
				: 'Disabled';
			$bulk_properties[ $bulk_property_label ] = $property_value;
		}

		return $bulk_properties;
	}

	private function get_parallel_processing_status() {
		return defined( 'WP_SMUSH_PARALLEL' ) && WP_SMUSH_PARALLEL ? 'Enabled' : 'Disabled';
	}

	private function get_smush_type(): string {
		if ( $this->settings->is_webp_module_active() ) {
			return 'WebP';
		}

		if ( $this->settings->is_avif_module_active() ) {
			return 'AVIF';
		}

		return 'Classic';
	}

	private function get_current_lossy_level_label() {
		$lossy_level = $this->settings->get_lossy_level_setting();
		$smush_modes = array(
			Settings::LEVEL_LOSSLESS    => 'Basic',
			Settings::LEVEL_SUPER_LOSSY => 'Super',
			Settings::LEVEL_ULTRA_LOSSY => 'Ultra',
		);
		if ( ! isset( $smush_modes[ $lossy_level ] ) ) {
			$lossy_level = Settings::LEVEL_LOSSLESS;
		}

		return $smush_modes[ $lossy_level ];
	}

	private function track_detection_feature_toggle( $setting_value ) {
		return $this->track_feature_toggle( $setting_value, 'Image Resize Detection' );
	}

	protected function track_webp_mod_feature_toggle( $setting_value ) {
		if ( $this->is_switching_next_gen_format() ) {
			return;
		}

		return $this->track_feature_toggle( $setting_value, 'Next-Gen' );
	}

	protected function track_avif_mod_feature_toggle( $setting_value ) {
		if ( $this->is_switching_next_gen_format() ) {
			return;
		}

		return $this->track_feature_toggle( $setting_value, 'Next-Gen' );
	}

	private function is_switching_next_gen_format() {
		return did_action( 'wp_smush_next_gen_before_format_switch' );
	}

	private function track_cdn_feature_toggle( $setting_value ) {
		return $this->track_feature_toggle( $setting_value, 'CDN' );
	}

	private function track_lazy_load_feature_toggle( $setting_value ) {
		$this->track_lazy_load_feature_updated_on_toggle( $setting_value );

		return $this->track_feature_toggle( $setting_value, 'Lazy Load' );
	}

	private function track_lazy_load_feature_updated_on_toggle( $activate ) {
		$this->track_lazy_load_updated(
			array(
				'update_type'       => $activate ? 'activate' : 'deactivate',
				'modified_settings' => 'na',
			),
			$this->settings->get_setting( 'wp-smush-lazy_load', array() )
		);
	}

	private function track_feature_toggle( $active, $feature ) {
		$event = $active
			? 'Feature Activated'
			: 'Feature Deactivated';

		$this->track( $event, array(
			'Feature'        => $feature,
			'Triggered From' => $this->identify_referrer(),
		) );

		return true;
	}

	private function get_next_gen_referer() {
		$page                   = $this->get_referer_page();
		$webp_configuration     = Webp_Configuration::get_instance();
		$is_user_on_wizard_webp = 'smush-next-gen' === $page
		                          && $webp_configuration->should_show_wizard()
		                          && ! $webp_configuration->direct_conversion_enabled();

		if ( $is_user_on_wizard_webp ) {
			return 'Wizard';
		}

		return $this->identify_referrer();
	}

	private function identify_referrer() {
		$onboarding_request = ! empty( $_REQUEST['action'] ) && 'smush_setup' === $_REQUEST['action'];
		if ( $onboarding_request ) {
			return 'Wizard';
		}

		$page           = $this->get_referer_page();
		$triggered_from = array(
			'smush'              => 'Dashboard',
			'smush-bulk'         => 'Bulk Smush',
			'smush-directory'    => 'Directory Smush',
			'smush-lazy-load'    => 'Lazy Load',
			'smush-cdn'          => 'CDN',
			'smush-next-gen'     => 'Next-Gen Formats',
			'smush-integrations' => 'Integrations',
			'smush-settings'     => 'Settings',
		);

		return empty( $triggered_from[ $page ] )
			? ''
			: $triggered_from[ $page ];
	}

	private function maybe_track_cdn_update( $settings ) {
		$cdn_properties      = array();
		$cdn_property_labels = $this->cdn_property_labels();
		foreach ( $settings as $setting_key => $setting_value ) {
			if ( array_key_exists( $setting_key, $cdn_property_labels ) ) {
				$property_label                    = $cdn_property_labels[ $setting_key ];
				$property_value                    = $setting_value ? 'Enabled' : 'Disabled';
				$cdn_properties[ $property_label ] = $property_value;
			}
		}

		if ( isset( $settings[ Settings::NEXT_GEN_CDN_KEY ] ) ) {
			$cdn_next_gen_conversions_mode = $this->settings->sanitize_cdn_next_gen_conversion_mode( $settings[ Settings::NEXT_GEN_CDN_KEY ] );
			$cdn_next_gen_conversions      = array(
				Settings::NONE_CDN_MODE => 'None',
				Settings::WEBP_CDN_MODE => 'WebP',
				Settings::AVIF_CDN_MODE => 'AVIF',
			);
			if ( ! isset( $cdn_next_gen_conversions[ $cdn_next_gen_conversions_mode ] ) ) {
				$cdn_next_gen_conversions_mode = Settings::NONE_CDN_MODE;
			}

			$cdn_properties['Next-Gen Conversions'] = $cdn_next_gen_conversions[ $cdn_next_gen_conversions_mode ];
		}

		if ( $cdn_properties ) {
			$this->track( 'CDN Updated', $cdn_properties );

			return true;
		}

		return false;
	}

	private function cdn_property_labels() {
		return array(
			'background_images' => 'Background Images',
			'auto_resize'       => 'Automatic Resizing',
			'rest_api_support'  => 'Rest API',
		);
	}

	public function track_directory_smush() {
		$this->track( 'Directory Smushed' );
	}

	public function track_bulk_smush_start() {
		$properties = $this->get_bulk_properties();
		$properties = array_merge(
			$properties,
			array(
				'process_id'              => $this->get_process_id(),
				'Background Optimization' => $this->get_background_optimization_status(),
				'Cron'                    => $this->get_cron_healthy_status(),
			)
		);
		$this->track( 'Bulk Smush Started', $properties );
	}

	private function get_process_id() {
		return md5( $this->media_library_last_process->get_process_start_time() );
	}

	/**
	 * Track the event on background optimization completed.
	 * Note: For ajax Bulk Smush, we will track it via js.
	 *
	 * @return void
	 */
	public function track_background_bulk_smush_completed() {
		$bg_optimization    = WP_Smush::get_instance()->core()->mod->bg_optimization;
		$total_items        = $bg_optimization->get_total_items();
		$failed_items       = $bg_optimization->get_failed_items();
		$failure_percentage = $total_items > 0 ? round( $failed_items * 100 / $total_items ) : 0;

		$properties = array_merge(
			$this->get_bulk_smush_stats(),
			array(
				'Total Enqueued Images' => $total_items,
				'Failure Percentage'    => $failure_percentage,
			)
		);
		$properties = $this->filter_bulk_smush_completed_properties( $properties );

		$this->track( 'Bulk Smush Completed', $properties );
	}

	/**
	 * Add extra properties to the bulk smush completed event for Bulk Smush include ajax method.
	 *
	 * @param array $properties Bulk Smush completed properties.
	 */
	protected function filter_bulk_smush_completed_properties( $properties ) {
		return array_merge(
			$properties,
			array(
				'process_id'              => $this->get_process_id(),
				'Background Optimization' => $this->get_background_optimization_status(),
				'Cron'                    => $this->get_cron_healthy_status(),
				'Time Elapsed'            => $this->media_library_last_process->get_process_elapsed_time(),
				'Smush Type'              => $this->get_smush_type(),
				'Mode'                    => $this->get_current_lossy_level_label(),
			)
		);
	}

	private function get_bulk_smush_stats() {
		$global_stats = WP_Smush::get_instance()->core()->get_global_stats();
		$array_util   = new Array_Utils();

		return array(
			'Total Savings'                 => $this->convert_to_megabytes( (int) $array_util->get_array_value( $global_stats, 'savings_bytes' ) ),
			'Total Images'                  => (int) $array_util->get_array_value( $global_stats, 'count_images' ),
			'Media Optimization Percentage' => (float) $array_util->get_array_value( $global_stats, 'percent_optimized' ),
			'Percentage of Savings'         => (float) $array_util->get_array_value( $global_stats, 'savings_percent' ),
			'Images Resized'                => (int) $array_util->get_array_value( $global_stats, 'count_resize' ),
			'Resize Savings'                => $this->convert_to_megabytes( (int) $array_util->get_array_value( $global_stats, 'savings_resize' ) ),
		);
	}

	public function track_config_applied( $config_name ) {
		$properties = $config_name
			? array( 'Config Name' => $config_name )
			: array();

		$properties['Triggered From'] = $this->identify_referrer();

		$this->track( 'Config Applied', $properties );
	}

	public function track_opt_toggle( $old_settings, $settings ) {
		$settings = $this->remove_unchanged_settings( $old_settings, $settings );

		if ( isset( $settings['usage'] ) ) {
			// Following the new change, the location for Opt In/Out is lowercase and none whitespace.
			// @see SMUSH-1538.
			$location = str_replace( ' ', '_', $this->identify_referrer() );
			$location = strtolower( $location );
			$this->track(
				$settings['usage'] ? 'Opt In' : 'Opt Out',
				array(
					'Location'       => $location,
					'active_plugins' => $this->get_active_plugins(),
				)
			);
		}
	}

	public function track_integrations_saved( $old_settings, $settings ) {
		if ( empty( $settings['usage'] ) ) {
			return;
		}

		$settings = $this->remove_unchanged_settings( $old_settings, $settings );
		if ( empty( $settings ) ) {
			return;
		}

		$this->maybe_track_integrations_toggle( $settings );
	}

	private function maybe_track_integrations_toggle( $settings ) {
		$integrations = array(
			'gutenberg'  => 'Gutenberg',
			'gform'      => 'Gravity Forms',
			'js_builder' => 'WP Bakery',
			's3'         => 'Amazon S3',
			'nextgen'    => 'NextGen Gallery',
		);

		foreach ( $settings as $integration_slug => $is_activated ) {
			if ( ! array_key_exists( $integration_slug, $integrations ) ) {
				continue;
			}

			if ( $is_activated ) {
				$this->track(
					'Integration Activated',
					array(
						'Integration' => $integrations[ $integration_slug ],
					)
				);
			} else {
				$this->track(
					'Integration Deactivated',
					array(
						'Integration' => $integrations[ $integration_slug ],
					)
				);
			}
		}
	}

	public function intercept_reset() {
		if ( $this->settings->get( 'usage' ) ) {
			$this->track(
				'Opt Out',
				array(
					'Location'       => 'reset',
					'active_plugins' => $this->get_active_plugins(),
				)
			);
		}
	}

	public function record_scan_death() {
		$this->scan_background_process_dead = $this->scan_background_process->get_status()->is_dead();
	}

	public function track_background_scan_start( $identifier, $background_process ) {
		$type = $this->scan_background_process_dead
			? 'Retry'
			: 'New';

		$this->_track_background_scan_start( $type, $background_process );
	}

	private function _track_background_scan_start( $type, $background_process ) {
		$properties = array(
			'Scan Type' => $type,
		);

		$this->track( 'Scan Started', array_merge(
			$properties,
			$this->get_bulk_properties(),
			$this->get_scan_properties()
		) );
	}

	/**
	 * @param $identifier
	 * @param $background_process Background_Process
	 *
	 * @return void
	 */
	public function track_background_scan_end( $identifier, $background_process ) {
		$properties = array(
			'Retry Attempts' => $background_process->get_revival_count(),
			'Time Elapsed'   => $this->media_library_last_process->get_process_elapsed_time(),
		);
		$this->track( 'Scan Ended', array_merge(
			$properties,
			$this->get_bulk_properties(),
			$this->get_scan_properties()
		) );
	}

	public function track_background_scan_process_death() {
		$this->track(
			'Background Process Dead',
			array_merge(
				array(
					'Process Type' => 'Scan',
					'Slice Size'   => $this->get_scanner_slice_size(),
					'Time Elapsed' => $this->media_library_last_process->get_process_elapsed_time(),
					'Smush Type'   => $this->get_smush_type(),
					'Mode'         => $this->get_current_lossy_level_label(),
				),
				$this->get_scan_background_process_properties()
			)
		);
	}

	public function track_bulk_smush_background_process_death() {
		$this->track(
			'Background Process Dead',
			array_merge(
				array(
					'Process Type' => 'Smush',
					'Slice Size'   => 0,
					'Time Elapsed' => $this->media_library_last_process->get_process_elapsed_time(),
					'Smush Type'   => $this->get_smush_type(),
					'Mode'         => $this->get_current_lossy_level_label(),
				),
				$this->get_bulk_background_process_properties()
			)
		);
	}

	private function get_scan_properties() {
		$global_stats       = Global_Stats::get();
		$global_stats_array = $global_stats->to_array();
		$properties         = array(
			'process_id' => $this->get_process_id(),
			'Slice Size' => $this->get_scanner_slice_size(),
		);

		$labels = array(
			'image_attachment_count' => 'Image Attachment Count',
			'optimized_images_count' => 'Optimized Images Count',
			'optimize_count'         => 'Optimize Count',
			'reoptimize_count'       => 'Reoptimize Count',
			'ignore_count'           => 'Ignore Count',
			'animated_count'         => 'Animated Count',
			'error_count'            => 'Error Count',
			'percent_optimized'      => 'Percent Optimized',
			'size_before'            => 'Size Before',
			'size_after'             => 'Size After',
			'savings_percent'        => 'Savings Percent',
		);

		$savings_keys = array(
			'size_before',
			'size_after',
		);

		foreach ( $labels as $key => $label ) {
			if ( isset( $global_stats_array[ $key ] ) ) {
				$properties[ $label ] = $global_stats_array[ $key ];

				if ( in_array( $key, $savings_keys, true ) ) {
					$properties[ $label ] = $this->convert_to_megabytes( $properties[ $label ] );
				}
			}
		}

		return $properties;
	}

	private function get_bulk_background_process_properties() {
		$bg_optimization = WP_Smush::get_instance()->core()->mod->bg_optimization;
		$process_id      = $this->get_process_id();

		if ( ! $bg_optimization->is_background_enabled() ) {
			return array(
				'process_id' => $process_id,
			);
		}

		$total_items     = $bg_optimization->get_total_items();
		$processed_items = $bg_optimization->get_processed_items();

		return array(
			'process_id'             => $process_id,
			'Retry Attempts'         => $bg_optimization->get_revival_count(),
			'Total Enqueued Images'  => $total_items,
			'Completion Percentage'  => $this->get_background_process_completion_percentage( $total_items, $processed_items ),
			'Total Processed Images' => $processed_items,
		);
	}

	private function get_scan_background_process_properties() {
		$query                  = new Media_Item_Query();
		$total_enqueued_images  = $query->get_image_attachment_count();
		$total_items            = $this->scan_background_process->get_status()->get_total_items();
		$processed_items        = $this->scan_background_process->get_status()->get_processed_items();
		$scanner_slice_size     = $this->get_scanner_slice_size();
		$total_processed_images = $processed_items * $scanner_slice_size;
		$total_processed_images = min( $total_processed_images, $total_enqueued_images );

		return array(
			'process_id'             => $this->get_process_id(),
			'Retry Attempts'         => $this->scan_background_process->get_revival_count(),
			'Total Enqueued Images'  => $total_enqueued_images,
			'Completion Percentage'  => $this->get_background_process_completion_percentage( $total_items, $processed_items ),
			'Total Processed Images' => $total_processed_images,
		);
	}

	private function get_background_process_completion_percentage( $total_items, $processed_items ) {
		if ( $total_items < 1 ) {
			return 0;
		}

		return ceil( $processed_items * 100 / $total_items );
	}

	private function convert_to_megabytes( $size_in_bytes ) {
		if ( empty( $size_in_bytes ) ) {
			return 0;
		}
		$unit_mb = pow( 1024, 2 );
		return round( $size_in_bytes / $unit_mb, 2 );
	}

	private function get_scanner_slice_size() {
		if ( is_null( $this->scanner_slice_size ) ) {
			$this->scanner_slice_size = ( new Media_Library_Scanner() )->get_slice_size();
		}

		return $this->scanner_slice_size;
	}

	public function track_toggle_next_gen_fallback( $old_settings, $settings ) {
		if ( empty( $settings['usage'] ) ) {
			return;
		}

		$webp_activated     = ! empty( $settings['webp_mod'] );
		$avif_activated     = ! empty( $settings['avif_mod'] );
		$next_gen_activated = $webp_activated || $avif_activated;
		// Do not track when Next Gen is not activated.
		if ( ! $next_gen_activated ) {
			return;
		}

		$modified_settings         = $this->remove_unchanged_settings( $old_settings, $settings );
		$next_gen_fallback_changed = isset( $modified_settings['webp_fallback'] ) || isset( $modified_settings['avif_fallback'] );
		// Do not track if both WebP and AVIF fallbacks are not changed.
		if ( ! $next_gen_fallback_changed ) {
			return;
		}

		$webp_fallback_activated = ! empty( $settings['webp_fallback'] );
		$avif_fallback_activated = ! empty( $settings['avif_fallback'] );
		// Do not track if both WebP and AVIF fallbacks have the same status while switching the Next-Gen formats.
		if ( $this->is_switching_next_gen_format() && ( $webp_fallback_activated === $avif_fallback_activated ) ) {
			return;
		}

		$next_gen_fallback_activated = ( $webp_activated && $webp_fallback_activated )
										|| ( $avif_activated && $avif_fallback_activated );

		$update_type         = $next_gen_fallback_activated ? 'browser_support_on' : 'browser_support_off';
		$next_gen_properties = $this->get_next_gen_properties();
		$next_gen_method     = 'avif_direct';
		if ( $webp_activated ) {
			$direct_conversion_enabled = ! empty( $settings['webp_direct_conversion'] );// WebP method might or might not be changed.
			$next_gen_method           = $direct_conversion_enabled ? 'webp_direct' : 'server_redirect';
		}

		$this->track(
			'next_gen_updated',
			array_merge(
				$next_gen_properties,
				array(
					'update_type' => $update_type,
					'Method'      => $next_gen_method,
				)
			)
		);
	}

	public function track_deleting_all_next_gen_files() {
		$auto_deleting_old_next_gen_files = wp_doing_cron();
		if ( $auto_deleting_old_next_gen_files ) {
			return;
		}

		$next_gen_properties = $this->get_next_gen_properties();
		$this->track(
			'next_gen_updated',
			array_merge(
				$next_gen_properties,
				array(
					'update_type' => 'delete_files',
				)
			)
		);
	}

	public function track_webp_method_changed() {
		$next_gen_properties = $this->get_next_gen_properties();
		$this->track(
			'next_gen_updated',
			array_merge(
				$next_gen_properties,
				array(
					'update_type' => 'switch_webp_method',
				)
			)
		);
	}

	public function track_next_gen_status_changed() {
		if ( $this->is_switching_next_gen_format() ) {
			return;
		}

		$next_gen_properties = $this->get_next_gen_properties();
		$update_type         = $this->next_gen_manager->is_active() ? 'activate' : 'deactivate';
		$this->track(
			'next_gen_updated',
			array_merge(
				$next_gen_properties,
				array(
					'update_type' => $update_type,
				)
			)
		);
	}

	/**
	 * Note: Uses shutdown action to ensure all new settings are updated.
	 */
	public function maybe_track_next_gen_format_changed() {
		$switched_next_gen_format = did_action( 'wp_smush_next_gen_after_format_switch' );
		if ( ! $switched_next_gen_format ) {
			return;
		}

		$next_gen_properties = $this->get_next_gen_properties();
		$this->track(
			'next_gen_updated',
			array_merge(
				$next_gen_properties,
				array(
					'update_type' => 'switch_next_gen_format',
				)
			)
		);
	}

	private function get_next_gen_properties() {
		$location                    = $this->get_next_gen_referer();
		$active_format_configuration = $this->next_gen_manager->get_active_format_configuration();
		$next_gen_status_notice      = $this->get_next_gen_status_notice();
		$next_gen_method             = 'avif_direct';
		if ( Webp_Configuration::FORMAT_KEY === $active_format_configuration->get_format_key() ) {
			// Directly check webp_direct_conversion option to identify webp method even webp module is disabled.
			$direct_conversion_enabled = $this->settings->get( 'webp_direct_conversion' );
			$next_gen_method           = $direct_conversion_enabled ? 'webp_direct' : 'webp_server';
		}

		return array(
			'Location'      => $location,
			'Method'        => $next_gen_method,
			'status_notice' => $next_gen_status_notice,
		);
	}

	private function get_next_gen_status_notice() {
		if ( ! $this->next_gen_manager->is_active() ) {
			return 'na';
		}

		if ( ! $this->next_gen_manager->is_configured() ) {
			$webp_configuration = Webp_Configuration::get_instance();
			return $webp_configuration->server_configuration()->get_configuration_error_code();
		}

		if ( is_multisite() ) {
			return 'active_subsite';// Activated but required run Bulk Smush on subsites.
		}

		$required_bulk_smush = Global_Stats::get()->is_outdated() || Global_Stats::get()->get_remaining_count() > 0;
		if ( $required_bulk_smush ) {
			return 'active_need_smush';
		}

		$auto_smush_enabled = $this->settings->is_automatic_compression_active();
		if ( $auto_smush_enabled ) {
			return 'active_automatic_enabled';
		}

		return 'active_automatic_disabled';
	}

	public function track_webp_reconfig() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$next_gen_properties = $this->get_next_gen_properties();
		$this->track(
			'next_gen_updated',
			array_merge(
				$next_gen_properties,
				array(
					'update_type' => 'reconfig',
				)
			)
		);
	}

	private function get_referer_page() {
		$path       = parse_url( wp_get_referer(), PHP_URL_QUERY );
		$query_vars = array();
		parse_str( $path, $query_vars );

		return empty( $query_vars['page'] ) ? '' : $query_vars['page'];
	}

	public function track_plugin_activation() {
		$this->track(
			'Opt In',
			array(
				'Location'       => 'reactivate',
				'active_plugins' => $this->get_active_plugins(),
			)
		);
	}

	public function track_plugin_deactivation() {
		$location = $this->get_deactivation_location();
		$this->track(
			'Opt Out',
			array(
				'Location'       => $location,
				'active_plugins' => $this->get_active_plugins(),
			)
		);
	}

	private function get_deactivation_location() {
		$is_hub_request = ! empty( $_REQUEST['wpmudev-hub'] );
		if ( $is_hub_request ) {
			return 'deactivate_hub';
		}

		$is_dashboard_request = wp_doing_ajax() &&
		                        ! empty( $_REQUEST['action'] ) &&
		                        'wdp-project-deactivate' === wp_unslash( $_REQUEST['action'] );

		if ( $is_dashboard_request ) {
			return 'deactivate_dashboard';
		}

		return 'deactivate_pluginlist';
	}

	private function get_active_plugins() {
		$active_plugins      = array();
		$active_plugin_files = $this->get_active_and_valid_plugin_files();
		foreach ( $active_plugin_files as $plugin_file ) {
			$plugin_name = $this->get_plugin_name( $plugin_file );
			if ( $plugin_name ) {
				$active_plugins[] = $plugin_name;
			}
		}

		return $active_plugins;
	}

	private function get_active_and_valid_plugin_files() {
		$active_plugins = is_multisite() ? wp_get_active_network_plugins() : array();
		$active_plugins = array_merge( $active_plugins, wp_get_active_and_valid_plugins() );

		return array_unique( $active_plugins );
	}

	private function get_plugin_name( $plugin_file ) {
		$plugin_data = get_plugin_data( $plugin_file );

		return ! empty( $plugin_data['Name'] ) ? $plugin_data['Name'] : '';
	}

	private function get_cron_healthy_status() {
		$is_cron_healthy = Background_Pre_Flight_Controller::get_instance()->is_cron_healthy();
		return $is_cron_healthy ? 'Enabled' : 'Disabled';
	}

	private function get_background_optimization_status() {
		$bg_optimization = WP_Smush::get_instance()->core()->mod->bg_optimization;
		return $bg_optimization->is_background_enabled() ? 'Enabled' : 'Disabled';
	}

	public function ajax_handle_track_request() {
		$event_name = $this->get_event_name();
		if ( ! check_ajax_referer( 'wp-smush-ajax' ) || ! Helper::is_user_allowed() || empty( $event_name ) ) {
			wp_send_json_error();
		}

		$properties = $this->get_event_properties( $event_name );

		if ( ! $this->allow_to_track( $event_name, $properties ) ) {
			wp_send_json_error();
		}

		$this->track(
			$event_name,
			$properties
		);

		wp_send_json_success();
	}

	private function allow_to_track( $event_name, $properties ) {
		$trackable_events   = array(
			'Setup Wizard'     => true,
			'smush_pro_upsell' => isset( $properties['Location'] ) && 'wizard' === $properties['Location'],
		);
		$is_trackable_event = ! empty( $trackable_events[ $event_name ] );

		return $is_trackable_event || $this->settings->get( 'usage' );
	}

	private function get_event_name() {
		return isset( $_POST['event'] ) ? sanitize_text_field( wp_unslash( $_POST['event'] ) ) : '';
	}

	private function get_event_properties( $event_name ) {
		$properties = isset( $_POST['properties'] ) && is_array( $_POST['properties'] ) ? wp_unslash( $_POST['properties'] ) : array();
		$properties = map_deep( $properties, 'sanitize_text_field' );

		$filter_callback = $this->get_filter_properties_callback( $event_name );
		if ( method_exists( $this, $filter_callback ) ) {
			$properties = call_user_func( array( $this, $filter_callback ), $properties );
		}

		return $properties;
	}

	private function get_filter_properties_callback( $event_name ) {
		$event_name = str_replace( ' ', '_', $event_name );
		$event_name = sanitize_key( $event_name );
		return "filter_{$event_name}_properties";
	}

	/**
	 * Filter properties for Scan Interrupted event.
	 *
	 * @param array $properties JS properties.
	 */
	protected function filter_scan_interrupted_properties( $properties ) {
		return array_merge(
			$properties,
			array(
				'Slice Size'              => $this->get_scanner_slice_size(),
				'Background Optimization' => $this->get_background_optimization_status(),
				'Cron'                    => $this->get_cron_healthy_status(),
				'Time Elapsed'            => $this->media_library_last_process->get_process_elapsed_time(),
				'Smush Type'              => $this->get_smush_type(),
				'Mode'                    => $this->get_current_lossy_level_label(),
				'WP Loopback Status'      => $this->get_wp_loopback_status( $properties ),
			),
			$this->get_scan_background_process_properties(),
			$this->get_last_image_process_properties()
		);
	}


	private function get_last_image_process_properties() {
		$last_image_id = $this->media_library_last_process->get_last_process_attachment_id();
		if ( ! $last_image_id ) {
			return array();
		}

		$media_item              = Media_Item_Cache::get_instance()->get( $last_image_id );
		$last_image_time_elapsed = $this->media_library_last_process->get_last_process_attachment_elapsed_time();
		$properties              = array(
			'Last Image Time Elapsed' => $last_image_time_elapsed,
		);

		if ( ! $media_item->is_valid() ) {
			return $properties;
		}

		$full_size = $media_item->get_full_or_scaled_size();
		if ( ! $full_size ) {
			return $properties;
		}

		$file_size    = $this->convert_to_megabytes( $full_size->get_filesize() );
		$image_width  = $full_size->get_width();
		$image_height = $full_size->get_height();
		$image_type   = strtoupper( $full_size->get_extension() );

		return array(
			'Last Image Time Elapsed' => $last_image_time_elapsed,
			'Last Image Size'         => $file_size,
			'Last Image Width'        => $image_width,
			'Last Image Height'       => $image_height,
			'Last Image Type'         => $image_type,
		);
	}

	/**
	 * Filter properties for Bulk Smush interrupted event.
	 *
	 * @param array $properties JS properties.
	 */
	protected function filter_bulk_smush_interrupted_properties( $properties ) {
		return array_merge(
			$properties,
			array(
				'Background Optimization' => $this->get_background_optimization_status(),
				'Cron'                    => $this->get_cron_healthy_status(),
				'Parallel Processing'     => $this->get_parallel_processing_status(),
				'Time Elapsed'            => $this->media_library_last_process->get_process_elapsed_time(),
				'Smush Type'              => $this->get_smush_type(),
				'Mode'                    => $this->get_current_lossy_level_label(),
				'WP Loopback Status'      => $this->get_wp_loopback_status( $properties ),
			),
			$this->get_bulk_background_process_properties(),
			$this->get_last_image_process_properties()
		);
	}

	public function ajax_track_deactivation_survey() {
		$event_name = $this->get_event_name();
		if ( ! check_ajax_referer( 'wp-smush-ajax' ) || ! Helper::is_user_allowed() || empty( $event_name ) ) {
			wp_send_json_error();
		}

		$properties = $this->get_event_properties( $event_name );
		$properties = array_merge(
			$properties,
			array(
				'active_features' => $this->get_active_features(),
				'active_plugins'  => $this->get_active_plugins(),
			)
		);

		$this->track(
			$event_name,
			$properties
		);

		wp_send_json_success();
	}

	private function get_active_features() {
		$lossy_level           = $this->settings->get_lossy_level_setting();
		$cdn_module_activated  = CDN_Helper::get_instance()->is_cdn_active();
		$webp_module_activated = ! $cdn_module_activated && $this->settings->is_webp_module_active();
		$avif_module_activated = ! $cdn_module_activated && $this->settings->is_avif_module_active();
		$webp_direct_activated = $webp_module_activated && $this->settings->is_webp_direct_conversion_active();
		$webp_server_activated = $webp_module_activated && ! $webp_direct_activated;

		$features = array(
			'lazy_load'        => $this->settings->is_lazyload_active(),
			'cdn'              => $cdn_module_activated,
			'avif'             => $avif_module_activated,
			'webp_direct'      => $webp_direct_activated,
			'webp_server'      => $webp_server_activated,
			'smush_basic'      => Settings::LEVEL_LOSSLESS === $lossy_level,
			'smush_super'      => Settings::LEVEL_SUPER_LOSSY === $lossy_level,
			'smush_ultra'      => Settings::LEVEL_ULTRA_LOSSY === $lossy_level,
			's3_offload'       => $this->settings->is_s3_active(),
			'wp_bakery'        => $this->settings->get( 'js_builder' ),
			'gravity_forms'    => $this->settings->get( 'gform' ),
			'nextgen_gallery'  => $this->settings->get( 'nextgen' ),
			'gutenberg_blocks' => $this->settings->get( 'gutenberg' ),
		);

		return array_keys( array_filter( $features ) );
	}

	private function get_wp_loopback_status( $properties ) {
		$is_loopback_error = ! empty( $properties['Trigger'] ) && 'loopback_error' === $properties['Trigger'];
		if ( $is_loopback_error ) {
			$loopback_status = Helper::loopback_supported() ? 'Pass' : 'Fail';
		} else {
			$loopback_status = 'na';
		}

		return $loopback_status;
	}

	public function track_bulk_smush_progress_stuck() {
		$properties = array(
			'Trigger'      => 'stuck_notice',
			'Modal Action' => 'na',
			'Troubleshoot' => 'na',
		);

		$properties = $this->filter_bulk_smush_interrupted_properties( $properties );

		$this->track( 'Bulk Smush Interrupted', $properties );
	}

	public function track_lazy_load_settings_updated( $old_settings, $settings ) {
		$changed_settings = $this->remove_unchanged_settings( (array) $old_settings, (array) $settings );

		$modified_settings = 'na';
		if ( ! empty( $changed_settings ) ) {
			$modified_settings_map = array(
				'format'            => 'media_type',
				'output'            => 'output_location',
				'animation'         => 'display_animation',
				'include'           => 'include_exclude_posttype',
				'exclude-pages'     => 'include_exclude_url',
				'exclude-classes'   => 'include_exclude_keyword',
				'footer'            => 'script_method',
				'native'            => 'native_lazyload',
				'noscript_fallback' => 'noscript',
			);

			$modified_settings = array_intersect_key( $modified_settings_map, $changed_settings );
			$modified_settings = ! empty( $modified_settings ) ? array_values( $modified_settings ) : 'na';
		}

		$this->track_lazy_load_updated(
			array(
				'update_type'       => 'modify',
				'modified_settings' => $modified_settings,
			),
			$settings
		);
	}

	private function track_lazy_load_updated( $properties, $settings ) {
		$exclusion_enabled         = $this->is_lazy_load_exclusion_enabled( $settings );
		$native_lazyload_enabled   = ! empty( $settings['native'] );
		$noscript_fallback_enabled = ! empty( $settings['noscript_fallback'] );
		$properties                = array_merge(
			array(
				'Location'           => $this->identify_referrer(),
				'exclusions'         => $exclusion_enabled ? 'Enabled' : 'Disabled',
				'native_lazy_status' => $native_lazyload_enabled ? 'Enabled' : 'Disabled',
				'noscript_status'    => $noscript_fallback_enabled ? 'Enabled' : 'Disabled',
			),
			$properties
		);

		$this->track( 'lazy_load_updated', $properties );
	}

	private function is_lazy_load_exclusion_enabled( $settings ) {
		if ( ! empty( $settings['exclude-pages'] ) || ! empty( $settings['exclude-classes'] ) ) {
			return true;
		}

		if ( empty( $settings['include'] ) || ! is_array( $settings['include'] ) ) {
			return false;
		}

		$included_post_types = $settings['include'];

		// By default, we activated for all post types, so this option is changed when any post type is unchecked.
		return in_array( false, $included_post_types, true );
	}
}