433 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * The Analytics Module
 | 
						|
 *
 | 
						|
 * @since      1.0.49
 | 
						|
 * @package    RankMath
 | 
						|
 * @subpackage RankMath\modules
 | 
						|
 * @author     Rank Math <support@rankmath.com>
 | 
						|
 */
 | 
						|
 | 
						|
namespace RankMath\Analytics;
 | 
						|
 | 
						|
use RankMath\Traits\Cache;
 | 
						|
 | 
						|
defined( 'ABSPATH' ) || exit;
 | 
						|
 | 
						|
/**
 | 
						|
 * Summary class.
 | 
						|
 */
 | 
						|
class Summary {
 | 
						|
 | 
						|
	use Cache;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Start date.
 | 
						|
	 *
 | 
						|
	 * @var string
 | 
						|
	 */
 | 
						|
	public $start_date;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * End date.
 | 
						|
	 *
 | 
						|
	 * @var string
 | 
						|
	 */
 | 
						|
	public $end_date;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Compare start date.
 | 
						|
	 *
 | 
						|
	 * @var string
 | 
						|
	 */
 | 
						|
	public $compare_start_date;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Compare end date.
 | 
						|
	 *
 | 
						|
	 * @var string
 | 
						|
	 */
 | 
						|
	public $compare_end_date;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Days.
 | 
						|
	 *
 | 
						|
	 * @var int
 | 
						|
	 */
 | 
						|
	public $days;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get Widget.
 | 
						|
	 *
 | 
						|
	 * @return object
 | 
						|
	 */
 | 
						|
	public function get_widget() {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		$cache_key = Stats::get()->get_cache_key( 'dashboard_stats_widget' );
 | 
						|
		$cache     = get_transient( $cache_key );
 | 
						|
 | 
						|
		if ( false !== $cache ) {
 | 
						|
			return $cache;
 | 
						|
		}
 | 
						|
 | 
						|
		$stats = DB::analytics()
 | 
						|
			->selectSum( 'impressions', 'impressions' )
 | 
						|
			->selectSum( 'clicks', 'clicks' )
 | 
						|
			->selectAvg( 'position', 'position' )
 | 
						|
			->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
 | 
						|
			->one();
 | 
						|
 | 
						|
		$old_stats = DB::analytics()
 | 
						|
			->selectSum( 'impressions', 'impressions' )
 | 
						|
			->selectSum( 'clicks', 'clicks' )
 | 
						|
			->selectAvg( 'position', 'position' )
 | 
						|
			->whereBetween( 'created', [ Stats::get()->compare_start_date, Stats::get()->compare_end_date ] )
 | 
						|
			->one();
 | 
						|
 | 
						|
		if ( is_null( $stats ) ) {
 | 
						|
			$stats = (object) [
 | 
						|
				'clicks'      => 0,
 | 
						|
				'impressions' => 0,
 | 
						|
				'position'    => 0,
 | 
						|
			];
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_null( $old_stats ) ) {
 | 
						|
			$old_stats = $stats;
 | 
						|
		}
 | 
						|
 | 
						|
		$stats->clicks = [
 | 
						|
			'total'      => (int) $stats->clicks,
 | 
						|
			'previous'   => (int) $old_stats->clicks,
 | 
						|
			'difference' => $stats->clicks - $old_stats->clicks,
 | 
						|
		];
 | 
						|
 | 
						|
		$stats->impressions = [
 | 
						|
			'total'      => (int) $stats->impressions,
 | 
						|
			'previous'   => (int) $old_stats->impressions,
 | 
						|
			'difference' => $stats->impressions - $old_stats->impressions,
 | 
						|
		];
 | 
						|
 | 
						|
		$stats->position = [
 | 
						|
			'total'      => (float) \number_format( $stats->position, 2 ),
 | 
						|
			'previous'   => (float) \number_format( $old_stats->position, 2 ),
 | 
						|
			'difference' => (float) \number_format( $stats->position - $old_stats->position, 2 ),
 | 
						|
		];
 | 
						|
 | 
						|
		$stats->keywords = $this->get_keywords_summary();
 | 
						|
 | 
						|
		$stats = apply_filters( 'rank_math/analytics/get_widget', $stats );
 | 
						|
 | 
						|
		set_transient( $cache_key, $stats, DAY_IN_SECONDS * Stats::get()->days );
 | 
						|
 | 
						|
		return $stats;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get Optimization stats.
 | 
						|
	 *
 | 
						|
	 * @param string $post_type Selected Post Type.
 | 
						|
	 *
 | 
						|
	 * @return object
 | 
						|
	 */
 | 
						|
	public function get_optimization_summary( $post_type = '' ) {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		$cache_group = 'rank_math_optimization_summary';
 | 
						|
		$cache_key   = $this->generate_hash( $post_type );
 | 
						|
		$cache       = $this->get_cache( $cache_key, $cache_group );
 | 
						|
		if ( false !== $cache ) {
 | 
						|
			return $cache;
 | 
						|
		}
 | 
						|
 | 
						|
		$stats = (object) [
 | 
						|
			'good'    => 0,
 | 
						|
			'ok'      => 0,
 | 
						|
			'bad'     => 0,
 | 
						|
			'noData'  => 0,
 | 
						|
			'total'   => 0,
 | 
						|
			'average' => 0,
 | 
						|
		];
 | 
						|
 | 
						|
		$object_type_sql = $post_type ? ' AND object_subtype = "' . $post_type . '"' : '';
 | 
						|
		$data            = $wpdb->get_results(
 | 
						|
			"SELECT COUNT(object_id) AS count,
 | 
						|
				CASE
 | 
						|
					WHEN seo_score BETWEEN 81 AND 100 THEN 'good'
 | 
						|
					WHEN seo_score BETWEEN 51 AND 80 THEN 'ok'
 | 
						|
					WHEN seo_score BETWEEN 1 AND 50 THEN 'bad'
 | 
						|
					WHEN seo_score = 0 THEN 'noData'
 | 
						|
					ELSE 'none'
 | 
						|
				END AS type
 | 
						|
			FROM {$wpdb->prefix}rank_math_analytics_objects
 | 
						|
			WHERE is_indexable = 1
 | 
						|
			{$object_type_sql}
 | 
						|
			GROUP BY type"
 | 
						|
		);
 | 
						|
 | 
						|
		$total = 0;
 | 
						|
		foreach ( $data as $row ) {
 | 
						|
			$total              += (int) $row->count;
 | 
						|
			$stats->{$row->type} = (int) $row->count;
 | 
						|
		}
 | 
						|
		$stats->total   = $total;
 | 
						|
		$stats->average = 0;
 | 
						|
 | 
						|
		// Average.
 | 
						|
		$query = DB::objects()
 | 
						|
		->selectCount( 'object_id', 'total' )
 | 
						|
		->where( 'is_indexable', 1 )
 | 
						|
		->selectSum( 'seo_score', 'score' );
 | 
						|
		if ( $object_type_sql ) {
 | 
						|
			$query->where( 'object_subtype', $post_type );
 | 
						|
		}
 | 
						|
 | 
						|
		$average         = $query->one();
 | 
						|
		$average->total += property_exists( $stats, 'noData' ) ? $stats->noData : 0; // phpcs:ignore
 | 
						|
		if ( $average->total > 0 ) {
 | 
						|
			$stats->average = \round( $average->score / $average->total, 2 );
 | 
						|
		}
 | 
						|
 | 
						|
		$this->set_cache( $cache_key, $stats, $cache_group, DAY_IN_SECONDS );
 | 
						|
 | 
						|
		return $stats;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get analytics summary.
 | 
						|
	 *
 | 
						|
	 * @return object
 | 
						|
	 */
 | 
						|
	public function get_analytics_summary() {
 | 
						|
		$args = [
 | 
						|
			'start_date'         => $this->start_date,
 | 
						|
			'end_date'           => $this->end_date,
 | 
						|
			'compare_start_date' => $this->compare_start_date,
 | 
						|
			'compare_end_date'   => $this->compare_end_date,
 | 
						|
		];
 | 
						|
 | 
						|
		$cache_group = 'rank_math_analytics_summary';
 | 
						|
		$cache_key   = $this->generate_hash( $args );
 | 
						|
		$cache       = $this->get_cache( $cache_key, $cache_group );
 | 
						|
		if ( false !== $cache ) {
 | 
						|
			return $cache;
 | 
						|
		}
 | 
						|
 | 
						|
		$stats = DB::analytics()
 | 
						|
			->selectCount( 'DISTINCT(page)', 'posts' )
 | 
						|
			->selectSum( 'impressions', 'impressions' )
 | 
						|
			->selectSum( 'clicks', 'clicks' )
 | 
						|
			->selectAvg( 'position', 'position' )
 | 
						|
			->whereBetween( 'created', [ $this->start_date, $this->end_date ] )
 | 
						|
			->one();
 | 
						|
		// Check validation.
 | 
						|
		$stats->clicks      = empty( $stats->clicks ) ? 0 : $stats->clicks;
 | 
						|
		$stats->impressions = empty( $stats->impressions ) ? 0 : $stats->impressions;
 | 
						|
		$stats->position    = empty( $stats->position ) ? 0 : $stats->position;
 | 
						|
 | 
						|
		$old_stats = DB::analytics()
 | 
						|
			->selectCount( 'DISTINCT(page)', 'posts' )
 | 
						|
			->selectSum( 'impressions', 'impressions' )
 | 
						|
			->selectSum( 'clicks', 'clicks' )
 | 
						|
			->selectAvg( 'position', 'position' )
 | 
						|
			->whereBetween( 'created', [ $this->compare_start_date, $this->compare_end_date ] )
 | 
						|
			->one();
 | 
						|
 | 
						|
		// Check validation.
 | 
						|
		$old_stats->clicks      = empty( $old_stats->clicks ) ? 0 : $old_stats->clicks;
 | 
						|
		$old_stats->impressions = empty( $old_stats->impressions ) ? 0 : $old_stats->impressions;
 | 
						|
		$old_stats->position    = empty( $old_stats->position ) ? 0 : $old_stats->position;
 | 
						|
 | 
						|
		$total_ctr    = 0 !== $stats->impressions ? round( ( $stats->clicks / $stats->impressions ) * 100, 2 ) : 0;
 | 
						|
		$previous_ctr = 0 !== $old_stats->impressions ? round( ( $old_stats->clicks / $old_stats->impressions ) * 100, 2 ) : 0;
 | 
						|
		$stats->ctr   = [
 | 
						|
			'total'      => $total_ctr,
 | 
						|
			'previous'   => $previous_ctr,
 | 
						|
			'difference' => $total_ctr - $previous_ctr,
 | 
						|
		];
 | 
						|
 | 
						|
		$stats->clicks = [
 | 
						|
			'total'      => (int) $stats->clicks,
 | 
						|
			'previous'   => (int) $old_stats->clicks,
 | 
						|
			'difference' => $stats->clicks - $old_stats->clicks,
 | 
						|
		];
 | 
						|
 | 
						|
		$stats->impressions = [
 | 
						|
			'total'      => (int) $stats->impressions,
 | 
						|
			'previous'   => (int) $old_stats->impressions,
 | 
						|
			'difference' => $stats->impressions - $old_stats->impressions,
 | 
						|
		];
 | 
						|
 | 
						|
		$stats->position = [
 | 
						|
			'total'      => (float) \number_format( $stats->position, 2 ),
 | 
						|
			'previous'   => (float) \number_format( $old_stats->position, 2 ),
 | 
						|
			'difference' => (float) \number_format( $stats->position - $old_stats->position, 2 ),
 | 
						|
		];
 | 
						|
		$stats->keywords = $this->get_keywords_summary();
 | 
						|
		$stats->graph    = $this->get_analytics_summary_graph();
 | 
						|
 | 
						|
		$stats = apply_filters( 'rank_math/analytics/summary', $stats );
 | 
						|
 | 
						|
		$stats = array_filter( (array) $stats );
 | 
						|
 | 
						|
		$this->set_cache( $cache_key, $stats, $cache_group, DAY_IN_SECONDS );
 | 
						|
 | 
						|
		return $stats;
 | 
						|
	}
 | 
						|
	/**
 | 
						|
	 * Get posts summary.
 | 
						|
	 *
 | 
						|
	 * @param string $post_type Selected Post Type.
 | 
						|
	 *
 | 
						|
	 * @return object
 | 
						|
	 */
 | 
						|
	public function get_posts_summary( $post_type = '' ) {
 | 
						|
		$cache_key = $this->get_cache_key( 'posts_summary', $this->days . 'days' );
 | 
						|
		$cache     = ! $post_type ? get_transient( $cache_key ) : false;
 | 
						|
 | 
						|
		if ( false !== $cache ) {
 | 
						|
			return $cache;
 | 
						|
		}
 | 
						|
 | 
						|
		global $wpdb;
 | 
						|
		$query   = DB::analytics()
 | 
						|
			->selectCount( 'DISTINCT(' . $wpdb->prefix . 'rank_math_analytics_gsc.page)', 'posts' )
 | 
						|
			->selectSum( 'impressions', 'impressions' )
 | 
						|
			->selectSum( 'clicks', 'clicks' )
 | 
						|
			->selectAvg( 'ctr', 'ctr' )
 | 
						|
			->whereBetween( $wpdb->prefix . 'rank_math_analytics_gsc.created', [ $this->start_date, $this->end_date ] );
 | 
						|
		$summary = $query->one();
 | 
						|
		$summary = apply_filters( 'rank_math/analytics/posts_summary', $summary, $post_type, $query );
 | 
						|
		$summary = wp_parse_args(
 | 
						|
			array_filter( (array) $summary ),
 | 
						|
			[
 | 
						|
				'ctr'         => 0,
 | 
						|
				'posts'       => 0,
 | 
						|
				'clicks'      => 0,
 | 
						|
				'pageviews'   => 0,
 | 
						|
				'impressions' => 0,
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		set_transient( $cache_key, $summary, DAY_IN_SECONDS );
 | 
						|
 | 
						|
		return $summary;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get keywords summary.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_keywords_summary() {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		// Get Total Keywords Counts.
 | 
						|
		$keywords_count = $wpdb->get_var(
 | 
						|
			$wpdb->prepare(
 | 
						|
				"SELECT COUNT(DISTINCT(query))
 | 
						|
				FROM {$wpdb->prefix}rank_math_analytics_gsc
 | 
						|
				WHERE created BETWEEN %s AND %s",
 | 
						|
				$this->start_date,
 | 
						|
				$this->end_date
 | 
						|
			)
 | 
						|
		);
 | 
						|
 | 
						|
		$old_keywords_count = $wpdb->get_var(
 | 
						|
			$wpdb->prepare(
 | 
						|
				"SELECT COUNT(DISTINCT(query))
 | 
						|
				FROM {$wpdb->prefix}rank_math_analytics_gsc
 | 
						|
				WHERE created BETWEEN %s AND %s",
 | 
						|
				$this->compare_start_date,
 | 
						|
				$this->compare_end_date
 | 
						|
			)
 | 
						|
		);
 | 
						|
 | 
						|
		$keywords = [
 | 
						|
			'total'      => (int) $keywords_count,
 | 
						|
			'previous'   => (int) $old_keywords_count,
 | 
						|
			'difference' => (int) $keywords_count - (int) $old_keywords_count,
 | 
						|
		];
 | 
						|
 | 
						|
		return $keywords;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get analytics graph data.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_analytics_summary_graph() {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		$data = new \stdClass();
 | 
						|
 | 
						|
		// Step1. Get splitted date intervals for graph within selected date range.
 | 
						|
		$intervals     = $this->get_intervals();
 | 
						|
		$sql_daterange = $this->get_sql_date_intervals( $intervals );
 | 
						|
 | 
						|
		// Step2. Get current analytics data by splitted date intervals.
 | 
						|
		// phpcs:disable
 | 
						|
		$query = $wpdb->prepare(
 | 
						|
			"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM(clicks) as clicks, SUM(impressions) as impressions, AVG(position) as position, AVG(ctr) as ctr, {$sql_daterange}
 | 
						|
			FROM {$wpdb->prefix}rank_math_analytics_gsc
 | 
						|
			WHERE created BETWEEN %s AND %s
 | 
						|
			GROUP BY range_group",
 | 
						|
			$this->start_date,
 | 
						|
			$this->end_date
 | 
						|
		);
 | 
						|
		$analytics = $wpdb->get_results( $query );
 | 
						|
		$analytics = $this->set_dimension_as_key( $analytics, 'range_group' );
 | 
						|
		// phpcs:enable
 | 
						|
 | 
						|
		// Step2. Get current keyword data by splitted date intervals. Keyword count should be calculated as total count of most recent date for each splitted date intervals.
 | 
						|
		// phpcs:disable
 | 
						|
		$query = $wpdb->prepare(
 | 
						|
			"SELECT t.range_group, MAX(CONCAT(t.range_group, ':', t.date, ':', t.keywords )) as mixed FROM
 | 
						|
				(SELECT COUNT(DISTINCT(query)) as keywords, Date(created) as date, {$sql_daterange}
 | 
						|
				FROM {$wpdb->prefix}rank_math_analytics_gsc
 | 
						|
				WHERE created BETWEEN %s AND %s
 | 
						|
				GROUP BY range_group, Date(created)) AS t
 | 
						|
			GROUP BY t.range_group",
 | 
						|
			$this->start_date,
 | 
						|
			$this->end_date
 | 
						|
		);
 | 
						|
		$keywords = $wpdb->get_results( $query );
 | 
						|
		// phpcs:enable
 | 
						|
 | 
						|
		$keywords = $this->extract_data_from_mixed( $keywords, 'mixed', ':', [ 'keywords', 'date' ] );
 | 
						|
		$keywords = $this->set_dimension_as_key( $keywords, 'range_group' );
 | 
						|
 | 
						|
		// merge metrics data.
 | 
						|
		$data->analytics = [];
 | 
						|
		$data->analytics = $this->get_merged_metrics( $analytics, $keywords, true );
 | 
						|
 | 
						|
		$data->merged = $this->get_date_array(
 | 
						|
			$intervals['dates'],
 | 
						|
			[
 | 
						|
				'clicks'      => [],
 | 
						|
				'impressions' => [],
 | 
						|
				'position'    => [],
 | 
						|
				'ctr'         => [],
 | 
						|
				'keywords'    => [],
 | 
						|
				'pageviews'   => [],
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		// Convert types.
 | 
						|
		$data->analytics = array_map( [ $this, 'normalize_graph_rows' ], $data->analytics );
 | 
						|
 | 
						|
		// Merge for performance.
 | 
						|
		$data->merged = $this->get_merge_data_graph( $data->analytics, $data->merged, $intervals['map'] );
 | 
						|
 | 
						|
		// For developers.
 | 
						|
		$data = apply_filters( 'rank_math/analytics/analytics_summary_graph', $data, $intervals );
 | 
						|
 | 
						|
		$data->merged = $this->get_graph_data_flat( $data->merged );
 | 
						|
		$data->merged = array_values( $data->merged );
 | 
						|
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
}
 |