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.
994 lines
27 KiB
PHTML
994 lines
27 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\Helper;
|
||
|
use RankMath\Traits\Hooker;
|
||
|
use RankMath\Helpers\Param;
|
||
|
use RankMathPro\Analytics\Pageviews;
|
||
|
use RankMath\Google\Console as Google_Analytics;
|
||
|
|
||
|
defined( 'ABSPATH' ) || exit;
|
||
|
|
||
|
/**
|
||
|
* Stats class.
|
||
|
*/
|
||
|
class Stats extends Keywords {
|
||
|
|
||
|
use Hooker;
|
||
|
|
||
|
/**
|
||
|
* Start timestamp.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public $start = 0;
|
||
|
|
||
|
/**
|
||
|
* End timestamp.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public $end = 0;
|
||
|
|
||
|
/**
|
||
|
* 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 = '';
|
||
|
|
||
|
/**
|
||
|
* Number of days.
|
||
|
*
|
||
|
* @var int
|
||
|
*/
|
||
|
public $days = 0;
|
||
|
|
||
|
/**
|
||
|
* Main instance
|
||
|
*
|
||
|
* Ensure only one instance is loaded or can be loaded.
|
||
|
*
|
||
|
* @return Stats
|
||
|
*/
|
||
|
public static function get() {
|
||
|
static $instance;
|
||
|
|
||
|
if ( is_null( $instance ) && ! ( $instance instanceof Stats ) ) {
|
||
|
$instance = new Stats();
|
||
|
$instance->set_date_range();
|
||
|
}
|
||
|
|
||
|
return $instance;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set date range.
|
||
|
*
|
||
|
* @param string $range Range of days.
|
||
|
*/
|
||
|
public function set_date_range( $range = false ) {
|
||
|
// Shift 3 days prior.
|
||
|
$subtract = DAY_IN_SECONDS * 3;
|
||
|
$end = strtotime( $this->do_filter( 'analytics/end_date', 'today' ) ) - $subtract;
|
||
|
$start = strtotime( false !== $range ? $range : $this->get_date_from_cookie( 'date_range', '-30 days' ), $end ) - $subtract;
|
||
|
|
||
|
// Timestamp.
|
||
|
$this->end = Helper::get_midnight( $end );
|
||
|
$this->start = Helper::get_midnight( $start );
|
||
|
|
||
|
// Period.
|
||
|
$this->end_date = Helper::get_date( 'Y-m-d 23:59:59', $end, false, true );
|
||
|
$this->start_date = Helper::get_date( 'Y-m-d 00:00:00', $start, false, true );
|
||
|
|
||
|
// Compare date.
|
||
|
$this->days = ceil( abs( $end - $start ) / DAY_IN_SECONDS );
|
||
|
$this->compare_end_date = $start - DAY_IN_SECONDS;
|
||
|
$this->compare_start_date = $this->compare_end_date - ( $this->days * DAY_IN_SECONDS );
|
||
|
$this->compare_end_date = Helper::get_date( 'Y-m-d 23:59:59', $this->compare_end_date, false, true );
|
||
|
$this->compare_start_date = Helper::get_date( 'Y-m-d 00:00:00', $this->compare_start_date, false, true );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get date intervals for graph.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_intervals() {
|
||
|
$range = $this->get_date_from_cookie( 'date_range', '-30 days' );
|
||
|
$interval = [
|
||
|
'-7 days' => '0 days',
|
||
|
'-15 days' => '-3 days',
|
||
|
'-30 days' => '-6 days',
|
||
|
'-3 months' => '-6 days',
|
||
|
'-6 months' => '-30 days',
|
||
|
'-1 year' => '-30 days',
|
||
|
];
|
||
|
|
||
|
$ticks = [
|
||
|
'-7 days' => 7,
|
||
|
'-15 days' => 5,
|
||
|
'-30 days' => 5,
|
||
|
'-3 months' => 13,
|
||
|
'-6 months' => 6,
|
||
|
'-1 year' => 12,
|
||
|
];
|
||
|
|
||
|
$addition = [
|
||
|
'-7 days' => 0,
|
||
|
'-15 days' => DAY_IN_SECONDS,
|
||
|
'-30 days' => DAY_IN_SECONDS,
|
||
|
'-3 months' => -DAY_IN_SECONDS / 6,
|
||
|
'-6 months' => DAY_IN_SECONDS / 2,
|
||
|
'-1 year' => 0,
|
||
|
];
|
||
|
|
||
|
$ticks = $ticks[ $range ];
|
||
|
$interval = $interval[ $range ];
|
||
|
$addition = $addition[ $range ];
|
||
|
|
||
|
$map = [];
|
||
|
$dates = [];
|
||
|
|
||
|
$end = $this->end;
|
||
|
$start = strtotime( $interval, $end );
|
||
|
|
||
|
for ( $i = 0; $i < $ticks; $i++ ) {
|
||
|
$end_date = Helper::get_date( 'Y-m-d', $end, false, true );
|
||
|
$start_date = Helper::get_date( 'Y-m-d', $start, false, true );
|
||
|
|
||
|
$dates[ $end_date ] = [
|
||
|
'start' => $start_date,
|
||
|
'end' => $end_date,
|
||
|
'formatted_date' => Helper::get_date( 'd M, Y', $end ),
|
||
|
'formatted_period' => Helper::get_date( 'd M', $start ) . ' - ' . Helper::get_date( 'd M, Y', $end ),
|
||
|
];
|
||
|
|
||
|
$map[ $start_date ] = $end_date;
|
||
|
for ( $j = 1; $j < 32; $j++ ) {
|
||
|
$date = Helper::get_date( 'Y-m-d', strtotime( $j . ' days', $start ), false, true );
|
||
|
if ( $start_date === $end_date ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( $date === $end_date ) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$map[ $date ] = $end_date;
|
||
|
}
|
||
|
$map[ $end_date ] = $end_date;
|
||
|
|
||
|
$end = \strtotime( '-1 days', $start );
|
||
|
$start = \strtotime( $interval, $end + $addition );
|
||
|
}
|
||
|
return [
|
||
|
'map' => $map,
|
||
|
'dates' => \array_reverse( $dates ),
|
||
|
];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get date intervals for SQL query.
|
||
|
*
|
||
|
* @param array $intervals Date Intervals.
|
||
|
* @param string $column Column name to check.
|
||
|
* @param string $newcolumn Column name to return.
|
||
|
* @return string
|
||
|
*/
|
||
|
public function get_sql_date_intervals( $intervals, $column = 'created', $newcolumn = 'range_group' ) {
|
||
|
$sql_parts = [];
|
||
|
array_push( $sql_parts, 'CASE' );
|
||
|
|
||
|
$index = 1;
|
||
|
foreach ( $intervals['dates'] as $date_range ) {
|
||
|
$start_date = $date_range['start'] . ' 00:00:00';
|
||
|
$end_date = $date_range['end'] . ' 23:59:59';
|
||
|
|
||
|
array_push( $sql_parts, sprintf( "WHEN %s BETWEEN '%s' AND '%s' THEN 'range%d'", $column, $start_date, $end_date, $index ) );
|
||
|
|
||
|
$index ++;
|
||
|
}
|
||
|
|
||
|
array_push( $sql_parts, "ELSE 'none'" );
|
||
|
array_push( $sql_parts, sprintf( "END AS '%s'", $newcolumn ) );
|
||
|
|
||
|
return implode( ' ', $sql_parts );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get date array
|
||
|
*
|
||
|
* @param array $dates Dates.
|
||
|
* @param array $default Default value.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_date_array( $dates, $default ) {
|
||
|
$data = [];
|
||
|
foreach ( $dates as $date => $d ) {
|
||
|
$data[ $date ] = $default;
|
||
|
$data[ $date ]['date'] = $date;
|
||
|
$data[ $date ]['dateFormatted'] = $d['start'] === $d['end'] ? $d['formatted_date'] : $d['formatted_period'];
|
||
|
$data[ $date ]['formattedDate'] = $d['formatted_date'];
|
||
|
}
|
||
|
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert data to proper type.
|
||
|
*
|
||
|
* @param array $row Row to normalize.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function normalize_graph_rows( $row ) {
|
||
|
foreach ( $row as $col => $val ) {
|
||
|
if ( in_array( $col, [ 'query', 'page', 'date', 'created', 'dateFormatted' ], true ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( in_array( $col, [ 'ctr', 'position', 'earnings' ], true ) ) {
|
||
|
$row->$col = round( $row->$col, 0 );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$row->$col = absint( $row->$col );
|
||
|
}
|
||
|
|
||
|
return $row;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove uncessary graph rows.
|
||
|
*
|
||
|
* @param array $rows Rows to filter.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function filter_graph_rows( $rows ) {
|
||
|
foreach ( $rows as $key => $row ) {
|
||
|
if ( isset( $row->range_group ) && 'none' === $row->range_group ) {
|
||
|
unset( $rows[ $key ] );
|
||
|
}
|
||
|
}
|
||
|
return $rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract proper data.
|
||
|
*
|
||
|
* @param array $rows Data rows.
|
||
|
* @param string $column Column name contains mixed data.
|
||
|
* @param string $sep Separator for mixed data.
|
||
|
* @param array $keys Column array to extract.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function extract_data_from_mixed( $rows, $column, $sep, $keys ) {
|
||
|
foreach ( $rows as $index => &$row ) {
|
||
|
if ( ! isset( $row->$column ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$mixed = explode( $sep, $row->$column );
|
||
|
$mixed_count = count( $mixed );
|
||
|
if ( ! $mixed_count ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
foreach ( $keys as $key_idx => $key ) {
|
||
|
if ( 'position' === $key ) {
|
||
|
// Should subtract the position value from 100. The position data was inverted before call this function.
|
||
|
$value = 100 - (int) $mixed[ $mixed_count - $key_idx - 1 ];
|
||
|
} else {
|
||
|
$value = $mixed[ $mixed_count - $key_idx - 1 ];
|
||
|
}
|
||
|
$row->$key = $value;
|
||
|
}
|
||
|
|
||
|
unset( $row->$column );
|
||
|
}
|
||
|
|
||
|
return $rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Merge two metrics array into one
|
||
|
*
|
||
|
* @param array $metrics_rows1 Metrics Rows to merge.
|
||
|
* @param array $metrics_rows2 Metrics Rows to merge.
|
||
|
* @param boolean $has_traffic Flag to include/exclude traffic data.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_merged_metrics( $metrics_rows1, $metrics_rows2, $has_traffic = false ) {
|
||
|
$data = [];
|
||
|
|
||
|
// Construct base data array.
|
||
|
$base_array = [
|
||
|
'position' => 0,
|
||
|
'diffPosition' => 0,
|
||
|
'clicks' => 0,
|
||
|
'diffClicks' => 0,
|
||
|
'impressions' => 0,
|
||
|
'diffImpressions' => 0,
|
||
|
'ctr' => 0,
|
||
|
'diffCtr' => 0,
|
||
|
];
|
||
|
|
||
|
if ( $has_traffic ) {
|
||
|
$base_array['pageviews'] = 0;
|
||
|
$base_array['difference'] = 0;
|
||
|
}
|
||
|
|
||
|
// Merge first array and second array into base array.
|
||
|
foreach ( $metrics_rows1 as $key => $row ) {
|
||
|
if ( isset( $metrics_rows2[ $key ] ) ) {
|
||
|
if ( is_object( $row ) ) {
|
||
|
$data[ $key ] = (object) array_merge( $base_array, (array) $row, (array) $metrics_rows2[ $key ] );
|
||
|
} else {
|
||
|
$data[ $key ] = array_merge( $base_array, $row, $metrics_rows2[ $key ] );
|
||
|
}
|
||
|
unset( $metrics_rows2[ $key ] );
|
||
|
} else {
|
||
|
$data[ $key ] = array_merge( $base_array, $row );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Merge remaining items from second array into base array.
|
||
|
foreach ( $metrics_rows2 as $key => $row ) {
|
||
|
if ( is_object( $row ) ) {
|
||
|
$metrics_rows2[ $key ] = (object) array_merge( $base_array, (array) $row );
|
||
|
} else {
|
||
|
$metrics_rows2[ $key ] = array_merge( $base_array, $row );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return array_merge( $data, $metrics_rows2 );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Merge data graph by date.
|
||
|
*
|
||
|
* @param array $rows Rows to merge.
|
||
|
* @param array $data Data array.
|
||
|
* @param array $map Interval map.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_merge_data_graph( $rows, $data, $map ) {
|
||
|
foreach ( $rows as $row ) {
|
||
|
if ( ! isset( $map[ $row->date ] ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$date = $map[ $row->date ];
|
||
|
foreach ( $row as $key => $value ) {
|
||
|
if ( 'date' === $key || 'created' === $key ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// trick to invert Position Graph YAxis.
|
||
|
if ( 'position' === $key ) {
|
||
|
$value = 0 - $value;
|
||
|
}
|
||
|
$data[ $date ][ $key ][] = $value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Flat graph data.
|
||
|
*
|
||
|
* @param array $rows Graph data.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_graph_data_flat( $rows ) {
|
||
|
foreach ( $rows as &$row ) {
|
||
|
if ( isset( $row['clicks'] ) ) {
|
||
|
$row['clicks'] = \array_sum( $row['clicks'] );
|
||
|
}
|
||
|
|
||
|
if ( isset( $row['impressions'] ) ) {
|
||
|
$row['impressions'] = \array_sum( $row['impressions'] );
|
||
|
}
|
||
|
|
||
|
if ( isset( $row['earnings'] ) ) {
|
||
|
$row['earnings'] = \array_sum( $row['earnings'] );
|
||
|
}
|
||
|
|
||
|
if ( isset( $row['pageviews'] ) ) {
|
||
|
$row['pageviews'] = \array_sum( $row['pageviews'] );
|
||
|
}
|
||
|
|
||
|
if ( isset( $row['ctr'] ) ) {
|
||
|
$row['ctr'] = empty( $row['ctr'] ) ? 0 : ceil( array_sum( $row['ctr'] ) / count( $row['ctr'] ) );
|
||
|
}
|
||
|
|
||
|
if ( isset( $row['position'] ) ) {
|
||
|
if ( empty( $row['position'] ) ) {
|
||
|
unset( $row['position'] );
|
||
|
} else {
|
||
|
$row['position'] = ceil( array_sum( $row['position'] ) / count( $row['position'] ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( isset( $row['keywords'] ) ) {
|
||
|
$row['keywords'] = empty( $row['keywords'] ) ? 0 : ceil( array_sum( $row['keywords'] ) / count( $row['keywords'] ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get filter data.
|
||
|
*
|
||
|
* @param string $filter Filter key.
|
||
|
* @param string $default Filter default value.
|
||
|
*
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function get_date_from_cookie( $filter, $default ) {
|
||
|
$cookie_key = 'rank_math_analytics_' . $filter;
|
||
|
$new_value = sanitize_title( Param::post( $filter ) );
|
||
|
if ( $new_value ) {
|
||
|
setcookie( $cookie_key, $new_value, time() + ( HOUR_IN_SECONDS * 30 ), COOKIEPATH, COOKIE_DOMAIN, false, true );
|
||
|
return $new_value;
|
||
|
}
|
||
|
|
||
|
if ( ! empty( $_COOKIE[ $cookie_key ] ) ) {
|
||
|
return $_COOKIE[ $cookie_key ];
|
||
|
}
|
||
|
|
||
|
return $default;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get analytics data.
|
||
|
*
|
||
|
* @param array $args Array of arguments.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_analytics_data( $args = [] ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
$args = wp_parse_args(
|
||
|
$args,
|
||
|
[
|
||
|
'dimension' => 'page',
|
||
|
'order' => 'DESC',
|
||
|
'orderBy' => 'diffPosition',
|
||
|
'objects' => false,
|
||
|
'pageview' => false,
|
||
|
'where' => '',
|
||
|
'sub_where' => '',
|
||
|
'pages' => [],
|
||
|
'type' => '',
|
||
|
'offset' => 0,
|
||
|
'perpage' => 5,
|
||
|
]
|
||
|
);
|
||
|
|
||
|
$dimension = $args['dimension'];
|
||
|
$type = $args['type'];
|
||
|
$offset = $args['offset'];
|
||
|
$perpage = $args['perpage'];
|
||
|
$order_by_field = $args['orderBy'];
|
||
|
$sub_where = $args['sub_where'];
|
||
|
|
||
|
$order_position_fields = [ 'position', 'diffPosition' ];
|
||
|
$order_metrics_fields = [ 'clicks', 'diffClicks', 'impressions', 'diffImpressions', 'ctr', 'diffCtr' ];
|
||
|
|
||
|
if ( in_array( $order_by_field, $order_position_fields, true ) ) {
|
||
|
// In case order by position related fields, get position data first.
|
||
|
$positions = $this->get_position_data_by_dimension( $args );
|
||
|
|
||
|
// Filter position data by condition.
|
||
|
$positions = $this->filter_analytics_data( $positions, $args );
|
||
|
|
||
|
// Get dimension list from above result.
|
||
|
$dimensions = wp_list_pluck( $positions, $dimension );
|
||
|
$dimensions = array_map( 'esc_sql', $dimensions );
|
||
|
|
||
|
// Get metrics data based on above dimension list.
|
||
|
$metrics = $this->get_metrics_data_by_dimension(
|
||
|
[
|
||
|
'dimension' => $dimension,
|
||
|
'sub_where' => ' AND ' . $dimension . " IN ('" . join( "', '", $dimensions ) . "')",
|
||
|
]
|
||
|
);
|
||
|
|
||
|
// Merge above two data into one.
|
||
|
$rows = $this->get_merged_metrics( $positions, $metrics, true );
|
||
|
|
||
|
} elseif ( in_array( $order_by_field, $order_metrics_fields, true ) ) {
|
||
|
// In case order by fields which are not related with position, get metrics data first.
|
||
|
$metrics = $this->get_metrics_data_by_dimension( $args );
|
||
|
|
||
|
// Filter metrics data by condition.
|
||
|
$metrics = $this->filter_analytics_data( $metrics, $args );
|
||
|
|
||
|
// Get dimension list from above result.
|
||
|
$dimensions = wp_list_pluck( $metrics, $dimension );
|
||
|
$dimensions = array_map( 'esc_sql', $dimensions );
|
||
|
|
||
|
// Get position data based on above dimension list.
|
||
|
$positions = $this->get_position_data_by_dimension(
|
||
|
[
|
||
|
'dimension' => $dimension,
|
||
|
'sub_where' => ' AND ' . $dimension . " IN ('" . join( "', '", $dimensions ) . "') " . $sub_where,
|
||
|
]
|
||
|
);
|
||
|
|
||
|
// Merge above two data into one.
|
||
|
$rows = $this->get_merged_metrics( $metrics, $positions, true );
|
||
|
} else {
|
||
|
// Get position data and other metrics data separately.
|
||
|
$positions = $this->get_position_data_by_dimension( $args );
|
||
|
$metrics = $this->get_metrics_data_by_dimension( $args );
|
||
|
|
||
|
// Merge above two data into one.
|
||
|
$rows = $this->get_merged_metrics( $positions, $metrics, true );
|
||
|
|
||
|
// Filter array by condition.
|
||
|
$rows = $this->filter_analytics_data( $rows, $args );
|
||
|
}
|
||
|
|
||
|
$page_urls = \array_merge( \array_keys( $rows ), $args['pages'] );
|
||
|
|
||
|
$pageviews = [];
|
||
|
if ( \class_exists( 'RankMathPro\Analytics\Pageviews' ) && $args['pageview'] && ! empty( $page_urls ) ) {
|
||
|
$pageviews = Pageviews::get_pageviews( [ 'pages' => $page_urls ] );
|
||
|
$pageviews = $pageviews['rows'];
|
||
|
}
|
||
|
|
||
|
if ( $args['objects'] ) {
|
||
|
$objects = $this->get_objects( $page_urls );
|
||
|
}
|
||
|
foreach ( $rows as $page => $row ) {
|
||
|
$rows[ $page ]['pageviews'] = [
|
||
|
'total' => 0,
|
||
|
'difference' => 0,
|
||
|
];
|
||
|
|
||
|
$rows[ $page ]['clicks'] = [
|
||
|
'total' => (int) $rows[ $page ]['clicks'],
|
||
|
'difference' => (int) $rows[ $page ]['diffClicks'],
|
||
|
];
|
||
|
|
||
|
$rows[ $page ]['impressions'] = [
|
||
|
'total' => (int) $rows[ $page ]['impressions'],
|
||
|
'difference' => (int) $rows[ $page ]['diffImpressions'],
|
||
|
];
|
||
|
|
||
|
$rows[ $page ]['position'] = [
|
||
|
'total' => (float) $rows[ $page ]['position'],
|
||
|
'difference' => (float) $rows[ $page ]['diffPosition'],
|
||
|
];
|
||
|
|
||
|
$rows[ $page ]['ctr'] = [
|
||
|
'total' => (float) $rows[ $page ]['ctr'],
|
||
|
'difference' => (float) $rows[ $page ]['diffCtr'],
|
||
|
];
|
||
|
|
||
|
unset(
|
||
|
$rows[ $page ]['diffClicks'],
|
||
|
$rows[ $page ]['diffImpressions'],
|
||
|
$rows[ $page ]['diffPosition'],
|
||
|
$rows[ $page ]['diffCtr'],
|
||
|
$rows[ $page ]['difference']
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if ( $args['pageview'] && ! empty( $pageviews ) ) {
|
||
|
foreach ( $pageviews as $pageview ) {
|
||
|
$page = $pageview['page'];
|
||
|
if ( ! isset( $rows[ $page ] ) ) {
|
||
|
$rows[ $page ] = [];
|
||
|
}
|
||
|
|
||
|
$rows[ $page ]['pageviews'] = [
|
||
|
'total' => (int) $pageview['pageviews'],
|
||
|
'difference' => (int) $pageview['difference'],
|
||
|
];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( $args['objects'] && ! empty( $objects ) ) {
|
||
|
foreach ( $objects as $object ) {
|
||
|
$page = $object['page'];
|
||
|
if ( ! isset( $rows[ $page ] ) ) {
|
||
|
$rows[ $page ] = [];
|
||
|
}
|
||
|
$rows[ $page ] = array_merge( $rows[ $page ], $object );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get position data.
|
||
|
*
|
||
|
* @param array $args Argument array.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_position_data_by_dimension( $args = [] ) {
|
||
|
global $wpdb;
|
||
|
|
||
|
$args = wp_parse_args(
|
||
|
$args,
|
||
|
[
|
||
|
'dimension' => 'page',
|
||
|
'where' => '',
|
||
|
'sub_where' => '',
|
||
|
]
|
||
|
);
|
||
|
|
||
|
$dimension = $args['dimension'];
|
||
|
$where = $args['where'];
|
||
|
$sub_where = $args['sub_where'];
|
||
|
|
||
|
if ( 'page' === $dimension ) {
|
||
|
// In case dimension is set as 'page', position data for each page will be top position of last ranked date.
|
||
|
// That is, among all the position value from the last date of the page, the top position(smallest position value) value will be the result.
|
||
|
|
||
|
// Get current position data.
|
||
|
// phpcs:disable
|
||
|
$query = $wpdb->prepare(
|
||
|
"SELECT {$dimension}, MAX(CONCAT({$dimension}, ':', DATE(created), ':', LPAD((100 - position), 3, '0'))) as uid
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc
|
||
|
WHERE created BETWEEN %s AND %s {$sub_where}
|
||
|
GROUP BY {$dimension}",
|
||
|
$this->start_date,
|
||
|
$this->end_date
|
||
|
);
|
||
|
$positions = $wpdb->get_results( $query );
|
||
|
|
||
|
// Get old position data.
|
||
|
$query = $wpdb->prepare(
|
||
|
"SELECT {$dimension}, MAX(CONCAT({$dimension}, ':', DATE(created), ':', LPAD((100 - position), 3, '0'))) as uid
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc
|
||
|
WHERE created BETWEEN %s AND %s
|
||
|
GROUP BY {$dimension}",
|
||
|
$this->compare_start_date,
|
||
|
$this->compare_end_date
|
||
|
);
|
||
|
$old_positions = $wpdb->get_results( $query );
|
||
|
// phpcs:enable
|
||
|
|
||
|
// Extract proper position data.
|
||
|
$positions = $this->extract_data_from_mixed( $positions, 'uid', ':', [ 'position', 'date' ] );
|
||
|
$old_positions = $this->extract_data_from_mixed( $old_positions, 'uid', ':', [ 'position', 'date' ] );
|
||
|
|
||
|
// Set 'page' as key.
|
||
|
$positions = $this->set_dimension_as_key( $positions, $dimension );
|
||
|
$old_positions = $this->set_dimension_as_key( $old_positions, $dimension );
|
||
|
|
||
|
// Calculate position difference, merge old into current position data array.
|
||
|
foreach ( $positions as $page => &$row ) {
|
||
|
$row = (array) $row; // force to convert as array.
|
||
|
if ( ! isset( $old_positions[ $page ] ) ) {
|
||
|
$old_position_value = 100; // Should set as 100 here to get correct position difference.
|
||
|
} else {
|
||
|
$old_position_value = $old_positions[ $page ]->position;
|
||
|
}
|
||
|
|
||
|
$row['diffPosition'] = $row['position'] - $old_position_value;
|
||
|
}
|
||
|
} else {
|
||
|
// In case dimension is not 'page', position data for each dimension will be most recent position value.
|
||
|
|
||
|
// Step1. Get most recent row id for each dimension for current data.
|
||
|
// phpcs:disable
|
||
|
$query = $wpdb->prepare(
|
||
|
"SELECT t1.id as id
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc t1
|
||
|
INNER JOIN (
|
||
|
SELECT query, MAX(created) as latest_created
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc
|
||
|
WHERE created BETWEEN %s AND %s {$sub_where} GROUP BY {$dimension}
|
||
|
) t2 ON t1.query = t2.query AND t1.created = t2.latest_created",
|
||
|
$this->start_date,
|
||
|
$this->end_date
|
||
|
);
|
||
|
$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 row id for each dimension for compare data.
|
||
|
// phpcs:disable
|
||
|
$query = $wpdb->prepare(
|
||
|
"SELECT t1.id as id
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc t1
|
||
|
INNER JOIN (
|
||
|
SELECT query, MAX(created) as latest_created
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc
|
||
|
WHERE created BETWEEN %s AND %s {$sub_where} GROUP BY {$dimension}
|
||
|
) t2 ON t1.query = t2.query AND t1.created = t2.latest_created",
|
||
|
$this->compare_start_date,
|
||
|
$this->compare_end_date
|
||
|
);
|
||
|
$old_ids = $wpdb->get_results( $query );
|
||
|
// phpcs:enable
|
||
|
|
||
|
// 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 position and difference data based on above id list.
|
||
|
// phpcs:disable
|
||
|
$positions = $wpdb->get_results(
|
||
|
"SELECT
|
||
|
t1.{$dimension} as {$dimension}, ROUND( t1.position, 0 ) as position,
|
||
|
COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) as diffPosition
|
||
|
FROM
|
||
|
( SELECT a.{$dimension}, a.position FROM {$wpdb->prefix}rank_math_analytics_gsc AS a WHERE 1 = 1{$ids_where}) AS t1
|
||
|
LEFT JOIN
|
||
|
( SELECT a.{$dimension}, a.position FROM {$wpdb->prefix}rank_math_analytics_gsc AS a WHERE 1 = 1{$old_ids_where}) AS t2
|
||
|
ON t1.{$dimension} = t2.{$dimension}
|
||
|
{$where}",
|
||
|
ARRAY_A
|
||
|
);
|
||
|
// phpcs:enable
|
||
|
|
||
|
$positions = $this->set_dimension_as_key( $positions, $dimension );
|
||
|
}
|
||
|
|
||
|
return $positions;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get metrics data.
|
||
|
*
|
||
|
* @param array $args Argument array.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function get_metrics_data_by_dimension( $args = [] ) {
|
||
|
global $wpdb;
|
||
|
Helper::enable_big_selects_for_queries();
|
||
|
$args = wp_parse_args(
|
||
|
$args,
|
||
|
[
|
||
|
'dimension' => 'page',
|
||
|
'sub_where' => '',
|
||
|
]
|
||
|
);
|
||
|
|
||
|
$dimension = $args['dimension'];
|
||
|
$sub_where = $args['sub_where'];
|
||
|
|
||
|
// Get metrics data like impressions, click, ctr, etc.
|
||
|
// phpcs:disable
|
||
|
$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(ctr) as ctr
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc
|
||
|
WHERE 1 = 1 AND created BETWEEN %s AND %s {$sub_where}
|
||
|
GROUP BY {$dimension}) as t1
|
||
|
LEFT JOIN
|
||
|
( SELECT {$dimension}, SUM( clicks ) as clicks, SUM(impressions) as impressions, AVG(ctr) as ctr
|
||
|
FROM {$wpdb->prefix}rank_math_analytics_gsc
|
||
|
WHERE 1 = 1 AND created BETWEEN %s AND %s {$sub_where}
|
||
|
GROUP BY {$dimension}) as t2
|
||
|
ON t1.{$dimension} = t2.{$dimension}",
|
||
|
$this->start_date,
|
||
|
$this->end_date,
|
||
|
$this->compare_start_date,
|
||
|
$this->compare_end_date
|
||
|
);
|
||
|
$metrics = $wpdb->get_results( $query, ARRAY_A );
|
||
|
// phpcs:enable
|
||
|
|
||
|
$metrics = $this->set_dimension_as_key( $metrics, $dimension );
|
||
|
|
||
|
return $metrics;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Filter analytics data.
|
||
|
*
|
||
|
* @param array $data Data to process.
|
||
|
* @param array $args Argument array.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function filter_analytics_data( $data, $args ) {
|
||
|
$dimension = $args['dimension'];
|
||
|
$offset = $args['offset'];
|
||
|
$perpage = $args['perpage'];
|
||
|
$order_by_field = $args['orderBy'];
|
||
|
|
||
|
/**
|
||
|
* Short-circuit to filter the data.
|
||
|
*/
|
||
|
$pre = $this->do_filter( 'analytics/pre_filter_data', null, $data, $args );
|
||
|
if ( is_array( $pre ) ) {
|
||
|
return $pre;
|
||
|
}
|
||
|
|
||
|
// Sort array by $args['order'], $order_by_field value.
|
||
|
if ( ! empty( $args['order'] ) ) {
|
||
|
$sort_base_arr = array_column( $data, $order_by_field, $dimension );
|
||
|
array_multisort( $sort_base_arr, 'ASC' === $args['order'] ? SORT_ASC : SORT_DESC, $data );
|
||
|
}
|
||
|
|
||
|
// Filter array by $offset, $perpage value.
|
||
|
$data = array_slice( $data, $offset, $perpage, true );
|
||
|
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set page as key.
|
||
|
*
|
||
|
* @param array $data Rows to process.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function set_page_as_key( $data ) {
|
||
|
$rows = [];
|
||
|
foreach ( $data as $row ) {
|
||
|
$page = $this->get_relative_url( $row['page'] );
|
||
|
if ( ! empty( $row['object_id'] ) && empty( $row['schemas_in_use'] ) ) {
|
||
|
$row['schemas_in_use'] = Helper::get_default_schema_type( $row['object_id'], true, true );
|
||
|
}
|
||
|
$rows[ $page ] = $row;
|
||
|
}
|
||
|
|
||
|
return $rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set dimension parameter as key.
|
||
|
*
|
||
|
* @param array $data Rows to process.
|
||
|
* @param string $dimension Dimension to set as key.
|
||
|
* @return array
|
||
|
*/
|
||
|
public function set_dimension_as_key( $data, $dimension = 'query' ) {
|
||
|
$rows = [];
|
||
|
foreach ( $data as $row ) {
|
||
|
if ( is_object( $row ) ) {
|
||
|
$value = $row->$dimension;
|
||
|
} else {
|
||
|
$value = $row[ $dimension ];
|
||
|
}
|
||
|
$key = 'page' === $dimension ? $this->get_relative_url( $value ) : strtolower( $value );
|
||
|
$rows[ $key ] = $row;
|
||
|
}
|
||
|
|
||
|
return $rows;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set query position history.
|
||
|
*
|
||
|
* @param array $data Rows to process.
|
||
|
* @param array $history Rows to process.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public function set_query_position( $data, $history ) {
|
||
|
foreach ( $history as $row ) {
|
||
|
$key = strtolower( $row->query );
|
||
|
|
||
|
$data[ $key ]['query'] = isset( $data[ $key ]['query'] ) ? $data[ $key ]['query'] : $key;
|
||
|
$data[ $key ]['graph'] = isset( $data[ $key ]['graph'] ) ? $data[ $key ]['graph'] : [];
|
||
|
|
||
|
if ( ! isset( $row->formatted_date ) ) {
|
||
|
$formatted_date = Helper::get_date( 'd M, Y', strtotime( $row->date ) );
|
||
|
$row->formatted_date = $formatted_date;
|
||
|
}
|
||
|
|
||
|
$data[ $row->query ]['graph'][] = $row;
|
||
|
}
|
||
|
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set page position history.
|
||
|
*
|
||
|
* @param array $data Rows to process.
|
||
|
* @param array $history Rows to process.
|
||
|
*
|
||
|
* @return array
|
||
|
*/
|
||
|
public function set_page_position_graph( $data, $history ) {
|
||
|
foreach ( $history as $row ) {
|
||
|
$data[ $row->page ]['graph'] = isset( $data[ $row->page ]['graph'] ) ? $data[ $row->page ]['graph'] : [];
|
||
|
|
||
|
if ( ! isset( $row->formatted_date ) ) {
|
||
|
$formatted_date = Helper::get_date( 'd M, Y', strtotime( $row->date ) );
|
||
|
$row->formatted_date = $formatted_date;
|
||
|
}
|
||
|
$data[ $row->page ]['graph'][] = $row;
|
||
|
|
||
|
}
|
||
|
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generate Cache Keys.
|
||
|
*
|
||
|
* @param string $what What for you need the key.
|
||
|
* @param mixed $args more salt to add into key.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function get_cache_key( $what, $args = [] ) {
|
||
|
$key = 'rank_math_' . $what;
|
||
|
|
||
|
if ( ! empty( $args ) ) {
|
||
|
$key .= '_' . join( '_', (array) $args );
|
||
|
}
|
||
|
|
||
|
return $key;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get relative url.
|
||
|
*
|
||
|
* @param string $url Url to make relative.
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function get_relative_url( $url ) {
|
||
|
$home_url = Google_Analytics::get_site_url();
|
||
|
|
||
|
// On multisite and sub-directory setup replace the home url.
|
||
|
if ( is_multisite() && ! is_subdomain_install() ) {
|
||
|
$url = \str_replace( $home_url, '/', $url );
|
||
|
} else {
|
||
|
$domain = strtolower( wp_parse_url( $home_url, PHP_URL_HOST ) );
|
||
|
$domain = str_replace( [ 'www.', '.' ], [ '', '\.' ], $domain );
|
||
|
$regex = "/http[s]?:\/\/(www\.)?$domain/mU";
|
||
|
$url = strtolower( trim( $url ) );
|
||
|
$url = preg_replace( $regex, '', $url );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Google API and get_permalink sends URL Encoded strings so we need
|
||
|
* to urldecode in order to get them to match with whats saved in DB.
|
||
|
*/
|
||
|
$url = urldecode( $url );
|
||
|
return \str_replace( $home_url, '', $url );
|
||
|
}
|
||
|
}
|