You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
12 KiB
PHTML
433 lines
12 KiB
PHTML
8 months ago
|
<?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;
|
||
|
}
|
||
|
}
|