File "class-hustle-wp-dashboard-page.php"

Full Path: /home/londdqdw/public_html/06/wp-content/plugins/wordpress-popup/inc/class-hustle-wp-dashboard-page.php
File size: 16.58 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * File for Hustle_Wp_Dashboard_Page class.
 *
 * @package Hustle
 * @since 4.2.1
 */

/**
 * Class Hustle_Wp_Dashboard_Page.
 * Handles scripts and actions for the WP Dashboard Page.
 *
 * @since 4.2.1
 */
class Hustle_Wp_Dashboard_Page {

	/**
	 * Hustle's settings for the WP Dashboard analytics widget.
	 *
	 * @since 4.2.1
	 * @var array
	 */
	private $settings;

	/**
	 * Class constructor.
	 *
	 * @since 4.2.1
	 * @param array $analytics_settings Hustle's settings for the WP Dashboard analytics widget.
	 */
	public function __construct( $analytics_settings ) {

		$this->settings = $analytics_settings;

		// Load styles for Dashboard.
		add_action( 'admin_print_styles', array( $this, 'register_dashboard_styles' ) );
		add_filter( 'admin_body_class', array( $this, 'admin_dashboard_body_class' ), 99 );

		// Load scripts for Dashboard.
		add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );

		add_action( 'wp_dashboard_setup', array( $this, 'analytics_widget_setup' ) );

		add_action( 'wp_ajax_hustle_get_wp_dashboard_widget_data', array( $this, 'get_wp_dashboard_widget_data' ) );
	}

	/**
	 * Registers styles for admin dashboard.
	 *
	 * @since 4.2.1
	 */
	public function register_dashboard_styles() {
		wp_register_style(
			'hstl-roboto',
			'https://fonts.bunny.net/css?family=Roboto+Condensed:300,300i,400,400i,700,700i|Roboto:300,300i,400,400i,500,500i,700,700i',
			array(),
			Opt_In::VERSION
		);

		wp_register_style(
			'hstl-opensans',
			'https://fonts.bunny.net/css?family=Open+Sans:400,400i,700,700i',
			array(),
			Opt_In::VERSION
		);

		wp_enqueue_style( 'hstl-roboto' );
		wp_enqueue_style( 'hstl-opensans' );

		wp_enqueue_style(
			'hustle_dashboard_styles',
			Opt_In::$plugin_url . 'assets/css/dashboard.min.css',
			array(),
			Opt_In::VERSION
		);
	}

	/**
	 * Registers scripts.
	 *
	 * @since 4.2.1
	 */
	public function register_scripts() {

		wp_enqueue_script(
			'chartjs',
			Opt_In::$plugin_url . 'assets/js/vendor/chartjs/chart.min.js',
			array(),
			'2.7.2',
			true
		);

		wp_register_script(
			'hustle_wp_dashboard_script',
			Opt_In::$plugin_url . 'assets/js/wp-dashboard.min.js',
			array( 'jquery', 'chartjs' ),
			Opt_In::VERSION,
			true
		);

		// Days labels for the chart.
		for ( $h = 89; $h >= 0; $h-- ) {
			$time         = strtotime( '-' . $h . ' days' );
			$days_array[] = gmdate( 'F d', $time );
		}

		// These are the labels for the different tracking types.
		$tracking_actions = array(
			'view'             => esc_html__( 'Views', 'hustle' ),
			'conversion'       => esc_html__( 'All Conversions', 'hustle' ),
			'cta_conversion'   => esc_html__( 'CTA Conversions', 'hustle' ),
			'optin_conversion' => esc_html__( 'Optin Conversions', 'hustle' ),
			'rate'             => esc_html__( 'Conversion Rate', 'hustle' ),
		);

		$data = array(
			'days_labels'         => $days_array,
			'tracking_actions'    => $tracking_actions,
			'active_module_types' => array_map( 'esc_html', (array) $this->settings['modules'] ),
			'loading'             => esc_html__( 'Loading...', 'hustle' ),
			'last_updated_ago'    => esc_html__( 'Last Updated - {time} ago', 'hustle' ),
		);

		wp_localize_script( 'hustle_wp_dashboard_script', 'hustleVars', $data );
		wp_enqueue_script( 'hustle_wp_dashboard_script' );
	}

	/**
	 * Modify admin dashboard body class to our own advantage.
	 *
	 * @since 4.2.1
	 *
	 * @param string $classes Current classes.
	 * @return mixed
	 */
	public function admin_dashboard_body_class( $classes ) {
		$classes .= ' sui-hustle-dashboard';

		return $classes;

	}

	/**
	 * Setup the Hustle analytics dashboard widgets.
	 *
	 * @since 4.1.0
	 */
	public function analytics_widget_setup() {

		if ( ! Hustle_Settings_Admin::global_tracking() ) {
			return;
		}

		$title = ! empty( $this->settings['title'] ) ? $this->settings['title'] : ' ';
		wp_add_dashboard_widget(
			'hustle_analytics',
			esc_html( $title ),
			array( $this, 'render_analytics_widget' )
		);
	}

	/**
	 * Outputs the Analytics dashboard widget.
	 *
	 * @since 4.1.0
	 *
	 * @param string $post Widget Control ID.
	 */
	public function render_analytics_widget( $post ) {

		$renderer = new Hustle_Layout_Helper( $this );
		$renderer->render(
			'admin/widget-analytics',
			array(
				'settings' => $this->settings,
			)
		);
	}

	/**
	 * Get analytics ranges for dashboard widget
	 *
	 * @since 4.2.1
	 * @return array
	 */
	public function get_analytic_ranges() {
		$ranges = array(
			7  => __( 'Last 7 days', 'hustle' ),
			30 => __( 'Last 30 days', 'hustle' ),
			90 => __( 'Last 90 days', 'hustle' ),
		);

		return $ranges;
	}

	/**
	 * Retrieves the data for the widget via AJAX.
	 *
	 * @since 4.2.1
	 */
	public function get_wp_dashboard_widget_data() {

		Opt_In_Utils::validate_ajax_call( 'hustle_update_wp_dashboard_chart' );

		$days_range    = filter_input( INPUT_POST, 'days', FILTER_VALIDATE_INT );
		$tracking_type = filter_input( INPUT_POST, 'trackingType', FILTER_SANITIZE_SPECIAL_CHARS );
		$delete_cache  = filter_input( INPUT_POST, 'delete_cache', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );

		$module_types_to_display = $this->settings['modules'];

		// The required fields are missing. Abort.
		if ( ! $days_range || ! $tracking_type || empty( $module_types_to_display ) ) {
			wp_send_json_error();
		}

		$allowed_ranges = array_keys( $this->get_analytic_ranges() );
		$days_range     = in_array( $days_range, $allowed_ranges, true ) ? $days_range : 7;

		$cache_group = 'hustle_dashboard_tracking_analytics';
		if ( $delete_cache ) {
			$widget_data = false;
			$this->delete_tracking_analytics_cache( $allowed_ranges, $cache_group );
		} else {
			// Try to retrieve the cached data.
			$widget_data = wp_cache_get( $days_range, $cache_group );
		}

		// Retrieve the data if not cached.
		if ( false === $widget_data ) {
			$widget_data = $this->get_formatted_tracking_data( $days_range );

			// Set last updated time.
			$widget_data['last_updated'] = time();
			$cache_expire                = $this->get_tracking_analytics_cache_expire();
			wp_cache_set( $days_range, $widget_data, $cache_group, $cache_expire );
		}

		// Set last updated time human difference.
		$widget_data['last_updated'] = human_time_diff( $widget_data['last_updated'] );

		wp_send_json_success( $widget_data );
	}

	/**
	 * Gets the data formatted as it's expected for the chart.
	 *
	 * @since 4.2.1
	 *
	 * @param integer $days_range Days before today.
	 * @return array
	 */
	private function get_formatted_tracking_data( $days_range ) {

		$tracking = Hustle_Tracking_Model::get_instance();

		// Daily stats for the period.
		$daily_stats = $this->get_and_format_daily_stats( $days_range, $tracking );

		// Totals from the previous range.
		$previous_range_totals = $this->get_and_format_prev_range_totals( $days_range, $tracking );

		// Overall totals to show, including the growth rate.
		$totals = $this->get_analytics_totals( $daily_stats, $previous_range_totals );

		$widget_data = array(
			'data'   => $daily_stats,
			'totals' => $totals,
		);

		return $widget_data;
	}

	/**
	 * Gets the raw values for the daily stats and formats it.
	 *
	 * @since 4.2.1
	 *
	 * @param integer               $days_range Number of days before today.
	 * @param Hustle_Tracking_Model $tracking Instance of the tracking model.
	 * @return array
	 */
	private function get_and_format_daily_stats( $days_range, $tracking ) {

		$raw_result = $tracking->get_wp_dash_daily_stats_data( $days_range );
		$final_data = $this->get_default_analytics_stats( $days_range );

		foreach ( $raw_result as $data ) {

			$module_type = $data['module_type'];
			$action      = $data['action'];

			$day = explode( ' ', $data['date_created'] )[0];

			// Use the default days as the base. Skip the current day if it's not defined.
			if ( ! isset( $final_data[ $module_type ][ $action ][ $day ] ) ) {
				continue;
			}
			$final_data[ $module_type ][ $action ][ $day ] += $data['counter'];
			$final_data['overall'][ $action ][ $day ]      += $data['counter'];

			// We have 2 conversion types: CTA and Optin. We also have an overall
			// count for conversions under simply 'conversion'. Sum these for that metric.
			if ( 'optin_conversion' === $action || 'cta_conversion' === $action ) {
				$final_data[ $module_type ]['conversion'][ $day ] += $data['counter'];
				$final_data['overall']['conversion'][ $day ]      += $data['counter'];
			}

			// Module types for embeds and ssharing module have the display type suffixed.
			// Map these to their main module type.
			if ( 0 === strpos( $module_type, 'embedded_' ) ) {
				$final_data['embedded'][ $action ][ $day ] += $data['counter'];

				// Same check as before: We merge CTA and Optin conversions into the 'conversion' key.
				if ( 'optin_conversion' === $action || 'cta_conversion' === $action ) {
					$final_data['embedded']['conversion'][ $day ] += $data['counter'];
				}
			}
			if ( 0 === strpos( $module_type, 'social_sharing_' ) ) {
				$final_data['social_sharing'][ $action ][ $day ] += $data['counter'];
			}
		}

		// Count rates.
		foreach ( $final_data as $block => $types ) {

			// Calculate the rates.
			foreach ( $types as $type => $days ) {
				if ( 'rate' !== $type ) {
					continue;
				}
				foreach ( $days as $day => $val ) {
					if ( ! empty( $final_data[ $block ]['view'][ $day ] ) && ! empty( $final_data[ $block ]['conversion'][ $day ] ) ) {
						$final_data[ $block ]['rate'][ $day ] = round( 100 * $final_data[ $block ]['conversion'][ $day ] / $final_data[ $block ]['view'][ $day ], 2 );
					}
				}
			}
		}

		return $final_data;
	}

	/**
	 * Gets default analytics stats with zero values.
	 *
	 * @since 4.1.0
	 *
	 * @param int $days_ago Number of days ago when to begin the stats.
	 * @return array
	 */
	private function get_default_analytics_stats( $days_ago ) {
		$days = array();
		$days_ago--;

		for ( $i = $days_ago; 0 <= $i; $i-- ) {
			$days[] = date( 'Y-m-d', time() - $i * DAY_IN_SECONDS ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
		}

		$all_blocks = array(
			'overall',
			'popup',
			'slidein',
			'embedded',
			'embedded_inline',
			'embedded_widget',
			'embedded_shortcode',
			'social_sharing',
			'social_sharing_floating',
			'social_sharing_inline',
			'social_sharing_widget',
			'social_sharing_shortcode',
		);

		$all_types  = array(
			'view',
			'conversion',
			'cta_conversion',
			'optin_conversion',
			'rate',
		);
		$final_data = array();
		foreach ( $all_blocks as $block ) {
			foreach ( $all_types as $type ) {
				foreach ( $days as $day ) {
					$final_data[ $block ][ $type ][ $day ] = 0;
				}
			}
		}

		return $final_data;
	}

	/**
	 * Gets the raw values for the previous range totals and formats it.
	 * This is used to define the growth rate.
	 *
	 * @since 4.2.1
	 *
	 * @param integer               $days_range Number of days before today.
	 * @param Hustle_Tracking_Model $tracking Instance of the tracking model.
	 * @return array
	 */
	private function get_and_format_prev_range_totals( $days_range, $tracking ) {

		$module_types_map = array(
			'popup'                    => Hustle_Module_Model::POPUP_MODULE,
			'slidein'                  => Hustle_Module_Model::SLIDEIN_MODULE,
			'embedded_inline'          => Hustle_Module_Model::EMBEDDED_MODULE,
			'embedded_shortcode'       => Hustle_Module_Model::EMBEDDED_MODULE,
			'embedded_widget'          => Hustle_Module_Model::EMBEDDED_MODULE,
			'social_sharing_inline'    => Hustle_Module_Model::SOCIAL_SHARING_MODULE,
			'social_sharing_shortcode' => Hustle_Module_Model::SOCIAL_SHARING_MODULE,
			'social_sharing_widget'    => Hustle_Module_Model::SOCIAL_SHARING_MODULE,
			'social_sharing_floating'  => Hustle_Module_Model::SOCIAL_SHARING_MODULE,
		);

		$default_action_counters = array(
			'view'             => 0,
			'conversion'       => 0,
			'cta_conversion'   => 0,
			'optin_conversion' => 0,
			'rate'             => 0,
		);

		$main_counter = array(
			'overall'                                  => $default_action_counters,
			Hustle_Module_Model::POPUP_MODULE          => $default_action_counters,
			Hustle_Module_Model::SLIDEIN_MODULE        => $default_action_counters,
			Hustle_Module_Model::EMBEDDED_MODULE       => $default_action_counters,
			Hustle_Module_Model::SOCIAL_SHARING_MODULE => $default_action_counters,
		);

		// Get the raw data from the tracking model.
		$raw_result = $tracking->get_per_module_type_totals_prev_range( $days_range );

		// Loop through the results, which are grouped by module type.
		// The possible module types are the keys from the array $module_types_map.
		foreach ( $raw_result as $data ) {

			// Not a valid module type, abort.
			if ( empty( $module_types_map[ $data->module_type ] ) ) {
				continue;
			}

			// Not a valid action, abort.
			if ( ! isset( $default_action_counters[ $data->action ] ) ) {
				continue;
			}

			$module_type = $module_types_map[ $data->module_type ];
			$action      = $data->action;
			$count       = $data->tracked_count;

			$main_counter[ $module_type ][ $action ] += $count;

			$main_counter['overall'][ $action ] += $count;
		}

		// Let's now calculate the total conversions (CTA + Optin), the conversion rate, and overalls.
		foreach ( $main_counter as $module_type => $action_counter ) {

			$conversion_count = $action_counter['optin_conversion'] + $action_counter['cta_conversion'];

			$rate        = 0;
			$total_views = $action_counter['view'];
			if ( $total_views && $conversion_count ) {
				$rate = round( ( $conversion_count / $total_views ) * 100, 2 );
			}

			$main_counter[ $module_type ]['rate'] = $rate;

			$main_counter[ $module_type ]['conversion'] = $conversion_count;
		}

		return $main_counter;
	}

	/**
	 * Calculate the totals from the numbers we already got.
	 * Here you get the per module total for each tracking
	 * action (cta conversion, optin conversion, view),
	 * as well as the growth rate compared to the previous
	 * period of the same current length.
	 *
	 * @since 4.2.1
	 *
	 * @param array $stats The per day tracking data already retrieved for the chart.
	 * @param array $previous_range_totals The totals for the previous range.
	 * @return array
	 */
	private function get_analytics_totals( $stats, $previous_range_totals ) {

		$total_count = array();
		foreach ( $stats as $module_type => $action_data ) {

			// Skip the module types with their display type attached. We won't be using these.
			if ( 0 === strpos( $module_type, 'embedded_' ) || 0 === strpos( $module_type, 'social_sharing_' ) ) {
				continue;
			}

			foreach ( $action_data as $tracking_action => $data ) {

				// We'll handle the conversion rate in a sec.
				if ( 'rate' === $tracking_action ) {
					continue;
				}

				$prev_period_total = $previous_range_totals[ $module_type ][ $tracking_action ];
				$current_total     = array_sum( $data );
				$growth            = $this->calculate_growth( $current_total, $prev_period_total );

				$total_count[ $module_type ][ $tracking_action ] = array(
					'total' => $current_total,
					'trend' => $growth,
				);

			}

			// Let's calculate the conversion rate.
			$rate              = 0;
			$total_views       = $total_count[ $module_type ]['view']['total'];
			$total_conversions = $total_count[ $module_type ]['conversion']['total'];
			if ( $total_views && $total_conversions ) {
				$rate = round( ( $total_conversions / $total_views ) * 100, 2 );
			}
			$prev_period_rate = $previous_range_totals[ $module_type ]['rate'];
			$rate_growth      = $this->calculate_growth( $rate, $prev_period_rate );

			$total_count[ $module_type ]['rate'] = array(
				'total' => $rate,
				'trend' => $rate_growth,
			);
		}

		return $total_count;
	}

	/**
	 * Calculates the growth between two values.
	 *
	 * @since 4.2.1
	 *
	 * @param int $current_total Value of the current range.
	 * @param int $prev_period_total Value of the previous range.
	 * @return int
	 */
	private function calculate_growth( $current_total, $prev_period_total ) {

		$growth = 0;
		if ( $current_total && $prev_period_total ) {

			$growth = ( $current_total - $prev_period_total ) / $prev_period_total * 100;
			$growth = round( $growth, 2 );
		}
		return $growth;
	}

	/**
	 * The cache expires at midnight, following WP's timezone.
	 * Return seconds to keep the cache.
	 *
	 * @since 4.4.6
	 * @return int
	 */
	private function get_tracking_analytics_cache_expire() {
		$timezone     = wp_timezone();
		$date         = new DateTime( 'tomorrow', $timezone );
		$cache_expire = $date->format( 'U' ) - time();

		return $cache_expire;
	}

	/**
	 * Delete tracking cache data
	 *
	 * @param array  $allowed_ranges array of ranges.
	 * @param string $cache_group cache key group.
	 *
	 * @since 4.4.6
	 */
	private function delete_tracking_analytics_cache( $allowed_ranges, $cache_group ) {
		foreach ( $allowed_ranges as $range ) {
			wp_cache_delete( $range, $cache_group );
		}
	}

}