753 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			753 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * The Analytics Module
 | 
						|
 *
 | 
						|
 * @since      2.0.0
 | 
						|
 * @package    RankMathPro
 | 
						|
 * @subpackage RankMathPro\modules
 | 
						|
 * @author     Rank Math <support@rankmath.com>
 | 
						|
 */
 | 
						|
 | 
						|
namespace RankMathPro\Analytics;
 | 
						|
 | 
						|
use WP_REST_Request;
 | 
						|
use RankMath\Traits\Cache;
 | 
						|
use RankMath\Traits\Hooker;
 | 
						|
use RankMath\Analytics\Stats;
 | 
						|
use RankMath\Helper;
 | 
						|
use MyThemeShop\Helpers\Param;
 | 
						|
 | 
						|
defined( 'ABSPATH' ) || exit;
 | 
						|
 | 
						|
/**
 | 
						|
 * Keywords class.
 | 
						|
 */
 | 
						|
class Keywords {
 | 
						|
 | 
						|
	use Hooker, Cache;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Main instance
 | 
						|
	 *
 | 
						|
	 * Ensure only one instance is loaded or can be loaded.
 | 
						|
	 *
 | 
						|
	 * @return Keywords
 | 
						|
	 */
 | 
						|
	public static function get() {
 | 
						|
		static $instance;
 | 
						|
 | 
						|
		if ( is_null( $instance ) && ! ( $instance instanceof Keywords ) ) {
 | 
						|
			$instance = new Keywords();
 | 
						|
			$instance->setup();
 | 
						|
		}
 | 
						|
 | 
						|
		return $instance;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Initialize filter.
 | 
						|
	 */
 | 
						|
	public function setup() {
 | 
						|
		$this->filter( 'rank_math/analytics/keywords', 'add_keyword_position_graph' );
 | 
						|
		$this->filter( 'rank_math/analytics/keywords_overview', 'add_winning_losing_data' );
 | 
						|
		$this->action( 'save_post', 'add_post_focus_keyword' );
 | 
						|
		$this->action( 'init', 'get_post_type_list', 99 );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get accessible post type lists to auto add the focus keywords.
 | 
						|
	 */
 | 
						|
	public function get_post_type_list() {
 | 
						|
		if ( 'rank-math-analytics' !== Param::get( 'page' ) ) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		$post_types = array_map(
 | 
						|
			function( $post_type ) {
 | 
						|
				return 'attachment' === $post_type ? false : Helper::get_post_type_label( $post_type );
 | 
						|
			},
 | 
						|
			Helper::get_accessible_post_types()
 | 
						|
		);
 | 
						|
		Helper::add_json( 'postTypes', array_filter( $post_types ) );
 | 
						|
		Helper::add_json( 'autoAddFK', Helper::get_settings( 'general.auto_add_focus_keywords', [] ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get keywords position data to show it in the graph.
 | 
						|
	 *
 | 
						|
	 * @param  array $rows Rows.
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function add_keyword_position_graph( $rows ) {
 | 
						|
		$history = $this->get_graph_data_for_keywords( \array_keys( $rows ) );
 | 
						|
		$rows    = Stats::get()->set_query_position( $rows, $history );
 | 
						|
 | 
						|
		return $rows;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get winning and losing keywords data.
 | 
						|
	 *
 | 
						|
	 * @param  array $data Data.
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function add_winning_losing_data( $data ) {
 | 
						|
		$data['winningKeywords'] = $this->get_winning_keywords();
 | 
						|
		$data['losingKeywords']  = $this->get_losing_keywords();
 | 
						|
 | 
						|
		if ( empty( $data['winningKeywords'] ) ) {
 | 
						|
			$data['winningKeywords']['response'] = 'No Data';
 | 
						|
		}
 | 
						|
		if ( empty( $data['losingKeywords'] ) ) {
 | 
						|
			$data['losingKeywords']['response'] = 'No Data';
 | 
						|
		}
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Extract keywords that can be added by removing the empty and the duplicate keywords.
 | 
						|
	 *
 | 
						|
	 * @param string $keywords Comma Separated Keyword List.
 | 
						|
	 *
 | 
						|
	 * @return array Keywords that can be added.
 | 
						|
	 */
 | 
						|
	public function extract_addable_track_keyword( $keywords ) {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		// Split keywords.
 | 
						|
		$keywords_to_add   = array_filter( array_map( 'trim', explode( ',', $keywords ) ) );
 | 
						|
		$keywords_to_check = array_filter( array_map( 'mb_strtolower', explode( ',', $keywords ) ) );
 | 
						|
 | 
						|
		// Check if keywords already exists.
 | 
						|
		$keywords_joined = "'" . join( "', '", array_map( 'esc_sql', $keywords_to_add ) ) . "'";
 | 
						|
		$query           = "SELECT keyword FROM {$wpdb->prefix}rank_math_analytics_keyword_manager as km WHERE km.keyword IN ( $keywords_joined )";
 | 
						|
		$data            = $wpdb->get_results( $query ); // phpcs:ignore
 | 
						|
 | 
						|
		// Filter out non-existing keywords.
 | 
						|
		foreach ( $data as $row ) {
 | 
						|
			$key = array_search( mb_strtolower( $row->keyword ), $keywords_to_check, true );
 | 
						|
			if ( false !== $key ) {
 | 
						|
				unset( $keywords_to_add[ $key ] );
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return $keywords_to_add;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Add keyword to Rank Tracker.
 | 
						|
	 *
 | 
						|
	 * @param array $keywords Keyword List.
 | 
						|
	 */
 | 
						|
	public function add_track_keyword( $keywords ) {
 | 
						|
		foreach ( $keywords as $add_keyword ) {
 | 
						|
			DB::keywords()->insert(
 | 
						|
				[
 | 
						|
					'keyword'    => $add_keyword,
 | 
						|
					'collection' => 'uncategorized',
 | 
						|
					'is_active'  => true,
 | 
						|
				],
 | 
						|
				[ '%s', '%s', '%d' ]
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Remove a keyword from Rank Tracker.
 | 
						|
	 *
 | 
						|
	 * @param string $keyword Keyword to remove.
 | 
						|
	 */
 | 
						|
	public function remove_track_keyword( $keyword ) {
 | 
						|
		DB::keywords()->where( 'keyword', $keyword )
 | 
						|
			->delete();
 | 
						|
 | 
						|
		delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Delete all tracked keywords.
 | 
						|
	 */
 | 
						|
	public function delete_all_tracked_keywords() {
 | 
						|
		DB::keywords()->delete();
 | 
						|
		delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
 | 
						|
	}
 | 
						|
	/**
 | 
						|
	 * Get tracked keywords count.
 | 
						|
	 *
 | 
						|
	 * @return int Total keywords count
 | 
						|
	 */
 | 
						|
	public function get_tracked_keywords_count() {
 | 
						|
		$total = DB::keywords()
 | 
						|
			->selectCount( 'DISTINCT(keyword)', 'total' )
 | 
						|
			->where( 'is_active', 1 )
 | 
						|
			->getVar();
 | 
						|
 | 
						|
		return (int) $total;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get keywords quota.
 | 
						|
	 *
 | 
						|
	 * @return array Keywords usage info.
 | 
						|
	 */
 | 
						|
	public function get_tracked_keywords_quota() {
 | 
						|
		$quota = (array) get_option(
 | 
						|
			'rank_math_keyword_quota',
 | 
						|
			[
 | 
						|
				'taken'     => 0,
 | 
						|
				'available' => 0,
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		return $quota;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get tracked keywords summary.
 | 
						|
	 *
 | 
						|
	 * @return array Keywords usage info.
 | 
						|
	 */
 | 
						|
	public function get_tracked_keywords_summary() {
 | 
						|
		$cache_key   = 'tracked_keywords_summary';
 | 
						|
		$cache_group = 'tracked_keywords_summary';
 | 
						|
		$summary     = $this->get_cache( $cache_key, $cache_group );
 | 
						|
 | 
						|
		if ( empty( $summary ) ) {
 | 
						|
			$summary          = $this->get_tracked_keywords_quota();
 | 
						|
			$summary['total'] = $this->get_tracked_keywords_count();
 | 
						|
			$this->set_cache( $cache_key, $summary, $cache_group, DAY_IN_SECONDS );
 | 
						|
		}
 | 
						|
 | 
						|
		return $summary;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get winning tracked keywords.
 | 
						|
	 *
 | 
						|
	 * @return array Top 5 winning tracked keywords data.
 | 
						|
	 */
 | 
						|
	public function get_tracked_winning_keywords() {
 | 
						|
		return $this->get_tracked_keywords(
 | 
						|
			[
 | 
						|
				'offset'  => 0,
 | 
						|
				'perpage' => 5,
 | 
						|
				'where'   => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) < 0',
 | 
						|
			]
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get losing tracked keywords.
 | 
						|
	 *
 | 
						|
	 * @return array Top 5 losing tracked keywords data.
 | 
						|
	 */
 | 
						|
	public function get_tracked_losing_keywords() {
 | 
						|
		return $this->get_tracked_keywords(
 | 
						|
			[
 | 
						|
				'order'   => 'DESC',
 | 
						|
				'offset'  => 0,
 | 
						|
				'perpage' => 5,
 | 
						|
				'where'   => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) > 0',
 | 
						|
			]
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get tracked keywords rows.
 | 
						|
	 *
 | 
						|
	 * @param WP_REST_Request $request Full details about the request.
 | 
						|
	 *
 | 
						|
	 * @return array Tracked keywords data.
 | 
						|
	 */
 | 
						|
	public function get_tracked_keywords_rows( WP_REST_Request $request ) {
 | 
						|
		$per_page = 25;
 | 
						|
 | 
						|
		$cache_args             = $request->get_params();
 | 
						|
		$cache_args['per_page'] = $per_page;
 | 
						|
 | 
						|
		$cache_group = 'rank_math_rest_tracked_keywords_rows';
 | 
						|
		$cache_key   = $this->generate_hash( $cache_args );
 | 
						|
		$result      = $this->get_cache( $cache_key, $cache_group );
 | 
						|
		if ( ! empty( $result ) ) {
 | 
						|
			return $result;
 | 
						|
		}
 | 
						|
 | 
						|
		$page    = ! empty( $request->get_param( 'page' ) ) ? $request->get_param( 'page' ) : 1;
 | 
						|
		$orderby = ! empty( $request->get_param( 'orderby' ) ) ? $request->get_param( 'orderby' ) : 'default';
 | 
						|
		$order   = ! empty( $request->get_param( 'order' ) ) ? strtoupper( $request->get_param( 'order' ) ) : 'DESC';
 | 
						|
		$keyword = ! empty( $request->get_param( 'search' ) ) ? filter_var( urldecode( $request->get_param( 'search' ) ), FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_BACKTICK ) : '';
 | 
						|
		$offset  = ( $page - 1 ) * $per_page;
 | 
						|
		$args    = wp_parse_args(
 | 
						|
			[
 | 
						|
 | 
						|
				'dimension' => 'query',
 | 
						|
				'limit'     => "LIMIT {$offset}, {$per_page}",
 | 
						|
				'keyword'   => $keyword,
 | 
						|
			]
 | 
						|
		);
 | 
						|
		switch ( $orderby ) {
 | 
						|
			case 'impressions':
 | 
						|
			case 'clicks':
 | 
						|
			case 'ctr':
 | 
						|
			case 'position':
 | 
						|
				$args['orderBy'] = $orderby;
 | 
						|
				$args['order']   = $order;
 | 
						|
				break;
 | 
						|
			case 'query':
 | 
						|
				$args['orderBy'] = 'keyword';
 | 
						|
				$args['order']   = $order;
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		$data    = $this->get_tracked_keywords_data( $args );
 | 
						|
		$data    = Stats::get()->set_dimension_as_key( $data );
 | 
						|
		$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
 | 
						|
		$data    = Stats::get()->set_query_position( $data, $history );
 | 
						|
 | 
						|
		if ( 'default' === $orderby ) {
 | 
						|
			uasort(
 | 
						|
				$data,
 | 
						|
				function( $a, $b ) use ( $orderby ) {
 | 
						|
					if ( false === array_key_exists( 'position', $a ) ) {
 | 
						|
						$a['position'] = [ 'total' => '0' ];
 | 
						|
					}
 | 
						|
					if ( false === array_key_exists( 'position', $b ) ) {
 | 
						|
						$b['position'] = [ 'total' => '0' ];
 | 
						|
					}
 | 
						|
 | 
						|
					if ( 0 === intval( $b['position']['total'] ) ) {
 | 
						|
						return 0;
 | 
						|
					}
 | 
						|
 | 
						|
					return $a['position']['total'] > $b['position']['total'];
 | 
						|
				}
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		$result['rowsData'] = $data;
 | 
						|
		// get total rows by search.
 | 
						|
		$args = wp_parse_args(
 | 
						|
			[
 | 
						|
 | 
						|
				'dimension' => 'query',
 | 
						|
				'limit'     => 'LIMIT 10000',
 | 
						|
				'keyword'   => $keyword,
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		if ( empty( $data ) ) {
 | 
						|
			$result['response'] = 'No Data';
 | 
						|
		} else {
 | 
						|
			$search_data     = $this->get_tracked_keywords_data( $args );
 | 
						|
			$result['total'] = count( $search_data );
 | 
						|
 | 
						|
			$this->set_cache( $cache_key, $result, $cache_group, DAY_IN_SECONDS );
 | 
						|
		}
 | 
						|
		return $result;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get keyword rows from keyword manager table.
 | 
						|
	 *
 | 
						|
	 * @param  array $args Array of arguments.
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_tracked_keywords_data( $args = [] ) {
 | 
						|
		global $wpdb;
 | 
						|
		Helper::enable_big_selects_for_queries();
 | 
						|
		$args = wp_parse_args(
 | 
						|
			$args,
 | 
						|
			[
 | 
						|
				'dimension' => 'query',
 | 
						|
				'order'     => 'ASC',
 | 
						|
				'orderBy'   => 'diffPosition1',
 | 
						|
				'objects'   => false,
 | 
						|
				'where'     => '',
 | 
						|
				'sub_where' => '',
 | 
						|
				'dates'     => ' AND created BETWEEN %s AND %s',
 | 
						|
				'limit'     => 'LIMIT 5',
 | 
						|
				'keyword'   => '',
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		$where       = $args['where'];
 | 
						|
		$limit       = $args['limit'];
 | 
						|
		$dimension   = $args['dimension'];
 | 
						|
		$sub_where   = $args['sub_where'];
 | 
						|
		$dates       = $args['dates'];
 | 
						|
		$keyword     = trim( $args['keyword'] );
 | 
						|
		$order       = sprintf( 'ORDER BY %s %s', $args['orderBy'], $args['order'] );
 | 
						|
		$dates_query = sprintf( " AND created BETWEEN '%s' AND '%s' ", Stats::get()->start_date, Stats::get()->end_date );
 | 
						|
		// Step1. Get most recent data row id for each keyword.
 | 
						|
		// phpcs:disable
 | 
						|
		$where_like_keyword = $wpdb->prepare( ' WHERE keyword LIKE %s', '%' . $wpdb->esc_like( $keyword ) . '%' );
 | 
						|
		if ( empty( $keyword ) ) {
 | 
						|
			$where_like_keyword = '';
 | 
						|
		}
 | 
						|
 | 
						|
		$query =  "SELECT MAX(id) as id FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1 {$dates_query} AND {$dimension} IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager {$where_like_keyword} GROUP BY keyword ) GROUP BY {$dimension}";
 | 
						|
		$ids = $wpdb->get_results( $query );
 | 
						|
		// phpcs:enable
 | 
						|
		// Step2. Get id list from above result.
 | 
						|
		$ids       = wp_list_pluck( $ids, 'id' );
 | 
						|
		$ids_where = " AND id IN ('" . join( "', '", $ids ) . "')";
 | 
						|
 | 
						|
		// Step3. Get most recent data row id for each keyword (for comparison).
 | 
						|
		// phpcs:disable
 | 
						|
		$dates_query = sprintf( " AND created BETWEEN '%s' AND '%s' ", Stats::get()->compare_start_date, Stats::get()->compare_end_date );
 | 
						|
		$query = "SELECT MAX(id) as id FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1 {$dates_query} AND {$dimension} IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager {$where_like_keyword} GROUP BY keyword ) GROUP BY {$dimension}";
 | 
						|
		$old_ids = $wpdb->get_results( $query );
 | 
						|
 | 
						|
		// Step4. Get id list from above result.
 | 
						|
		$old_ids       = wp_list_pluck( $old_ids, 'id' );
 | 
						|
		$old_ids_where = " AND id IN ('" . join( "', '", $old_ids ) . "')";
 | 
						|
 | 
						|
		// Step5. Get most performing keywords first based on id list from above.
 | 
						|
		$where_like_keyword1 = $wpdb->prepare( ' WHERE km.keyword LIKE %s', '%' . $wpdb->esc_like( $keyword ) . '%' );
 | 
						|
		if ( empty( $keyword ) ) {
 | 
						|
			$where_like_keyword1 = '';
 | 
						|
		}
 | 
						|
 | 
						|
		$positions = $wpdb->get_results(
 | 
						|
			"SELECT DISTINCT(km.keyword) as {$dimension}, COALESCE(t.position, 0) as position, COALESCE(t.diffPosition, 0) as diffPosition, COALESCE(t.diffPosition, 100) as diffPosition1, COALESCE(t.impressions, 0) as impressions, COALESCE(t.diffImpressions, 0) as diffImpressions, COALESCE(t.clicks, 0) as clicks, COALESCE(t.diffClicks, 0) as diffClicks, COALESCE(t.ctr, 0) as ctr, COALESCE(t.diffCtr, 0) as diffCtr
 | 
						|
			FROM {$wpdb->prefix}rank_math_analytics_keyword_manager km
 | 
						|
			LEFT JOIN (
 | 
						|
				SELECT
 | 
						|
					t1.{$dimension} as {$dimension}, ROUND( t1.position, 0 ) as position, ROUND( t1.impressions, 0 ) as impressions, ROUND( t1.clicks, 0 ) as clicks, ROUND( t1.ctr, 0 ) as ctr,
 | 
						|
					COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) as diffPosition,
 | 
						|
					COALESCE( ROUND( t1.impressions - COALESCE( t2.impressions, 100 ), 0 ), 0 ) as diffImpressions,
 | 
						|
					COALESCE( ROUND( t1.clicks - COALESCE( t2.clicks, 100 ), 0 ), 0 ) as diffClicks,
 | 
						|
					COALESCE( ROUND( t1.ctr - COALESCE( t2.ctr, 100 ), 0 ), 0 ) as diffCtr
 | 
						|
				FROM
 | 
						|
					(SELECT a.{$dimension}, a.position, a.impressions,a.clicks,a.ctr FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
 | 
						|
					 WHERE 1 = 1{$ids_where}) AS t1
 | 
						|
				LEFT JOIN
 | 
						|
					(SELECT a.{$dimension}, a.position, a.impressions,a.clicks,a.ctr FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
 | 
						|
					 WHERE 1 = 1{$old_ids_where}) AS t2
 | 
						|
				ON t1.{$dimension} = t2.{$dimension}) AS t on t.{$dimension} = km.keyword
 | 
						|
				{$where_like_keyword1}
 | 
						|
			{$where}
 | 
						|
			{$order}
 | 
						|
			{$limit}",
 | 
						|
			ARRAY_A
 | 
						|
		);
 | 
						|
		// phpcs:enable
 | 
						|
 | 
						|
		// Step6. Get keywords list from above results.
 | 
						|
		$keywords = array_column( $positions, 'query' );
 | 
						|
		$keywords = array_map( 'esc_sql', $keywords );
 | 
						|
		$keywords = array_map( 'strtolower', $keywords );
 | 
						|
		$keywords = '(\'' . join( '\', \'', $keywords ) . '\')';
 | 
						|
 | 
						|
		// step7. Get other metrics data.
 | 
						|
		$query   = $wpdb->prepare(
 | 
						|
			"SELECT t1.{$dimension} as {$dimension}, t1.clicks, t1.impressions, t1.ctr,
 | 
						|
				COALESCE( t1.clicks - t2.clicks, 0 ) as diffClicks,
 | 
						|
				COALESCE( t1.impressions - t2.impressions, 0 ) as diffImpressions,
 | 
						|
				COALESCE( t1.ctr - t2.ctr, 0 ) as diffCtr
 | 
						|
			FROM
 | 
						|
				( SELECT {$dimension}, SUM( clicks ) as clicks, SUM(impressions) as impressions, AVG(position) as position, AVG(ctr) as ctr FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1{$dates} AND {$dimension} IN {$keywords} GROUP BY {$dimension}) as t1
 | 
						|
			LEFT JOIN
 | 
						|
				( SELECT {$dimension}, SUM( clicks ) as clicks, SUM(impressions) as impressions, AVG(position) as position, AVG(ctr) as ctr FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1{$dates} AND {$dimension} IN {$keywords} GROUP BY {$dimension}) as t2
 | 
						|
			ON t1.query = t2.query",
 | 
						|
			Stats::get()->start_date,
 | 
						|
			Stats::get()->end_date,
 | 
						|
			Stats::get()->compare_start_date,
 | 
						|
			Stats::get()->compare_end_date
 | 
						|
		);
 | 
						|
		$metrics = $wpdb->get_results( $query, ARRAY_A );
 | 
						|
 | 
						|
		// Step8. Merge above two results.
 | 
						|
		$positions = Stats::get()->set_dimension_as_key( $positions, $dimension );
 | 
						|
		$metrics   = Stats::get()->set_dimension_as_key( $metrics, $dimension );
 | 
						|
		$data      = Stats::get()->get_merged_metrics( $positions, $metrics );
 | 
						|
 | 
						|
		// Step9. Construct return data.
 | 
						|
		foreach ( $data as $keyword => $row ) {
 | 
						|
			$data[ $keyword ]['graph'] = [];
 | 
						|
 | 
						|
			$data[ $keyword ]['clicks'] = [
 | 
						|
				'total'      => (int) $data[ $keyword ]['clicks'],
 | 
						|
				'difference' => (int) $data[ $keyword ]['diffClicks'],
 | 
						|
			];
 | 
						|
 | 
						|
			$data[ $keyword ]['impressions'] = [
 | 
						|
				'total'      => (int) $data[ $keyword ]['impressions'],
 | 
						|
				'difference' => (int) $data[ $keyword ]['diffImpressions'],
 | 
						|
			];
 | 
						|
 | 
						|
			$data[ $keyword ]['position'] = [
 | 
						|
				'total'      => (float) $data[ $keyword ]['position'],
 | 
						|
				'difference' => (float) $data[ $keyword ]['diffPosition'],
 | 
						|
			];
 | 
						|
 | 
						|
			$data[ $keyword ]['ctr'] = [
 | 
						|
				'total'      => (float) $data[ $keyword ]['ctr'],
 | 
						|
				'difference' => (float) $data[ $keyword ]['diffCtr'],
 | 
						|
			];
 | 
						|
 | 
						|
			unset(
 | 
						|
				$data[ $keyword ]['diffClicks'],
 | 
						|
				$data[ $keyword ]['diffImpressions'],
 | 
						|
				$data[ $keyword ]['diffPosition'],
 | 
						|
				$data[ $keyword ]['diffCtr']
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get tracked keywords.
 | 
						|
	 *
 | 
						|
	 * @param  array $args Array of arguments.
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_tracked_keywords( $args = [] ) {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		$args = wp_parse_args(
 | 
						|
			$args,
 | 
						|
			[
 | 
						|
				'dimension' => 'query',
 | 
						|
				'order'     => 'ASC',
 | 
						|
				'orderBy'   => 'diffPosition',
 | 
						|
				'offset'    => 0,
 | 
						|
				'perpage'   => 20000,
 | 
						|
				'sub_where' => " AND query IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager )",
 | 
						|
			]
 | 
						|
		);
 | 
						|
 | 
						|
		$data    = Stats::get()->get_analytics_data( $args );
 | 
						|
		$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
 | 
						|
		$data    = Stats::get()->set_query_position( $data, $history );
 | 
						|
 | 
						|
		// Add remaining keywords.
 | 
						|
		if ( 5 !== $args['perpage'] ) {
 | 
						|
			$rows = DB::keywords()->get();
 | 
						|
			foreach ( $rows as $row ) {
 | 
						|
				if ( ! isset( $data[ $row->keyword ] ) ) {
 | 
						|
					$data[ $row->keyword ] = [
 | 
						|
						'query'       => $row->keyword,
 | 
						|
						'graph'       => [],
 | 
						|
						'clicks'      => [
 | 
						|
							'total'      => 0,
 | 
						|
							'difference' => 0,
 | 
						|
						],
 | 
						|
						'impressions' => [
 | 
						|
							'total'      => 0,
 | 
						|
							'difference' => 0,
 | 
						|
						],
 | 
						|
						'position'    => [
 | 
						|
							'total'      => 0,
 | 
						|
							'difference' => 0,
 | 
						|
						],
 | 
						|
						'ctr'         => [
 | 
						|
							'total'      => 0,
 | 
						|
							'difference' => 0,
 | 
						|
						],
 | 
						|
						'pageviews'   => [
 | 
						|
							'total'      => 0,
 | 
						|
							'difference' => 0,
 | 
						|
						],
 | 
						|
					];
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get most recent day's keywords.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_recent_keywords() {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		$query = $wpdb->prepare(
 | 
						|
			"SELECT query
 | 
						|
			FROM {$wpdb->prefix}rank_math_analytics_gsc
 | 
						|
			WHERE DATE(created) = (SELECT MAX(DATE(created)) FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE created BETWEEN %s AND %s)
 | 
						|
			GROUP BY query",
 | 
						|
			Stats::get()->start_date,
 | 
						|
			Stats::get()->end_date
 | 
						|
		);
 | 
						|
		$data = $wpdb->get_results( $query ); // phpcs:ignore
 | 
						|
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get top 5 winning keywords.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_winning_keywords() {
 | 
						|
		$cache_key = Stats::get()->get_cache_key( 'winning_keywords', Stats::get()->days . 'days' );
 | 
						|
		$cache     = get_transient( $cache_key );
 | 
						|
 | 
						|
		if ( false !== $cache ) {
 | 
						|
			return $cache;
 | 
						|
		}
 | 
						|
 | 
						|
		// Get most recent day's keywords only.
 | 
						|
		$keywords = $this->get_recent_keywords();
 | 
						|
		$keywords = wp_list_pluck( $keywords, 'query' );
 | 
						|
		$keywords = array_map( 'strtolower', $keywords );
 | 
						|
		$data     = Stats::get()->get_analytics_data(
 | 
						|
			[
 | 
						|
				'order'     => 'ASC',
 | 
						|
				'dimension' => 'query',
 | 
						|
				'where'     => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) < 0',
 | 
						|
			]
 | 
						|
		);
 | 
						|
		$history  = $this->get_graph_data_for_keywords( \array_keys( $data ) );
 | 
						|
		$data     = Stats::get()->set_query_position( $data, $history );
 | 
						|
 | 
						|
		set_transient( $cache_key, $data, DAY_IN_SECONDS );
 | 
						|
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get top 5 losing keywords.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_losing_keywords() {
 | 
						|
		$cache_key = Stats::get()->get_cache_key( 'losing_keywords', Stats::get()->days . 'days' );
 | 
						|
		$cache     = get_transient( $cache_key );
 | 
						|
 | 
						|
		if ( false !== $cache ) {
 | 
						|
			return $cache;
 | 
						|
		}
 | 
						|
 | 
						|
		// Get most recent day's keywords only.
 | 
						|
		$keywords = $this->get_recent_keywords();
 | 
						|
		$keywords = wp_list_pluck( $keywords, 'query' );
 | 
						|
		$keywords = array_map( 'strtolower', $keywords );
 | 
						|
 | 
						|
		$data    = Stats::get()->get_analytics_data(
 | 
						|
			[
 | 
						|
				'dimension' => 'query',
 | 
						|
				'where'     => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) > 0',
 | 
						|
			]
 | 
						|
		);
 | 
						|
		$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
 | 
						|
		$data    = Stats::get()->set_query_position( $data, $history );
 | 
						|
 | 
						|
		set_transient( $cache_key, $data, DAY_IN_SECONDS );
 | 
						|
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get keywords graph data.
 | 
						|
	 *
 | 
						|
	 * @param array $keywords Keywords to get data for.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_graph_data_for_keywords( $keywords ) {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		$intervals     = Stats::get()->get_intervals();
 | 
						|
		$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
 | 
						|
		$keywords      = \array_map( 'esc_sql', $keywords );
 | 
						|
		$keywords      = '(\'' . join( '\', \'', $keywords ) . '\')';
 | 
						|
 | 
						|
		$query = $wpdb->prepare(
 | 
						|
			"SELECT a.query, a.position, t.date, t.range_group
 | 
						|
			FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
 | 
						|
			INNER JOIN
 | 
						|
				(SELECT query, DATE_FORMAT(created, '%%Y-%%m-%%d') as date, MAX(id) as id, {$sql_daterange}
 | 
						|
				FROM {$wpdb->prefix}rank_math_analytics_gsc
 | 
						|
				WHERE created BETWEEN %s AND %s
 | 
						|
					AND query IN {$keywords}
 | 
						|
				GROUP BY query, range_group
 | 
						|
				ORDER BY created ASC) AS t ON a.id = t.id
 | 
						|
			",
 | 
						|
			Stats::get()->start_date,
 | 
						|
			Stats::get()->end_date
 | 
						|
		);
 | 
						|
		$data  = $wpdb->get_results( $query );
 | 
						|
		// phpcs:enable
 | 
						|
 | 
						|
		$data = Stats::get()->filter_graph_rows( $data );
 | 
						|
 | 
						|
		return array_map( [ Stats::get(), 'normalize_graph_rows' ], $data );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get pages by keyword.
 | 
						|
	 *
 | 
						|
	 * @param  WP_REST_Request $request Full details about the request.
 | 
						|
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 | 
						|
	 */
 | 
						|
	public function get_keyword_pages( WP_REST_Request $request ) {
 | 
						|
		global $wpdb;
 | 
						|
 | 
						|
		$query = $wpdb->prepare(
 | 
						|
			"SELECT DISTINCT g.page
 | 
						|
			FROM {$wpdb->prefix}rank_math_analytics_gsc as g
 | 
						|
			WHERE g.query = %s AND g.created BETWEEN %s AND %s
 | 
						|
			ORDER BY g.created DESC
 | 
						|
			LIMIT 5",
 | 
						|
			$request->get_param( 'query' ),
 | 
						|
			Stats::get()->start_date,
 | 
						|
			Stats::get()->end_date
 | 
						|
		);
 | 
						|
 | 
						|
		$data    = $wpdb->get_results( $query ); // phpcs:ignore
 | 
						|
		$pages   = wp_list_pluck( $data, 'page' );
 | 
						|
		$console = Stats::get()->get_analytics_data(
 | 
						|
			[
 | 
						|
				'objects'   => true,
 | 
						|
				'pageview'  => true,
 | 
						|
				'sub_where' => " AND page IN ('" . join( "', '", $pages ) . "')",
 | 
						|
			]
 | 
						|
		);
 | 
						|
		return $console;
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Add focus keywords to Rank Tracker.
 | 
						|
	 *
 | 
						|
	 * @param  int $post_id Post ID.
 | 
						|
	 * @return mixed
 | 
						|
	 */
 | 
						|
	public function add_post_focus_keyword( $post_id ) {
 | 
						|
		if ( wp_is_post_revision( $post_id ) ) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		$auto_add_fks = Helper::get_settings( 'general.auto_add_focus_keywords', [] );
 | 
						|
		if (
 | 
						|
			empty( $auto_add_fks['enable_auto_import'] ) ||
 | 
						|
			empty( $auto_add_fks['post_types'] ) ||
 | 
						|
			! in_array( get_post_type( $post_id ), $auto_add_fks['post_types'], true )
 | 
						|
		) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		$focus_keyword = Helper::get_post_meta( 'focus_keyword', $post_id );
 | 
						|
		if ( empty( $focus_keyword ) ) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		$keywords_data = [];
 | 
						|
		$keywords      = explode( ',', $focus_keyword );
 | 
						|
		if ( ! empty( $auto_add_fks['secondary_keyword'] ) ) {
 | 
						|
			$keywords_data = $keywords;
 | 
						|
		} else {
 | 
						|
			$keywords_data[] = current( $keywords );
 | 
						|
		}
 | 
						|
 | 
						|
		DB::bulk_insert_query_focus_keyword_data( $keywords_data );
 | 
						|
	}
 | 
						|
}
 |