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.
652 lines
16 KiB
PHP
652 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* The Analytics module database operations
|
|
*
|
|
* @since 2.0.0
|
|
* @package RankMathPro
|
|
* @subpackage RankMathPro\modules
|
|
* @author Rank Math <support@rankmath.com>
|
|
*/
|
|
|
|
namespace RankMathPro\Analytics;
|
|
|
|
use RankMath\Helper;
|
|
use RankMath\Admin\Admin_Helper;
|
|
use RankMath\Google\Analytics as Analytics_Free;
|
|
use RankMath\Analytics\Stats;
|
|
use RankMathPro\Google\Adsense;
|
|
use RankMathPro\Analytics\Keywords;
|
|
use MyThemeShop\Helpers\Str;
|
|
use MyThemeShop\Database\Database;
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* DB class.
|
|
*/
|
|
class DB {
|
|
|
|
/**
|
|
* Get any table.
|
|
*
|
|
* @param string $table_name Table name.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function table( $table_name ) {
|
|
return Database::table( $table_name );
|
|
}
|
|
|
|
/**
|
|
* Get console data table.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function analytics() {
|
|
return Database::table( 'rank_math_analytics_gsc' );
|
|
}
|
|
|
|
/**
|
|
* Get analytics data table.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function traffic() {
|
|
return Database::table( 'rank_math_analytics_ga' );
|
|
}
|
|
|
|
/**
|
|
* Get adsense data table.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function adsense() {
|
|
return Database::table( 'rank_math_analytics_adsense' );
|
|
}
|
|
|
|
/**
|
|
* Get objects table.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function objects() {
|
|
return Database::table( 'rank_math_analytics_objects' );
|
|
}
|
|
|
|
/**
|
|
* Get inspections table.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function inspections() {
|
|
return Database::table( 'rank_math_analytics_inspections' );
|
|
}
|
|
|
|
/**
|
|
* Get links table.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function links() {
|
|
return Database::table( 'rank_math_internal_meta' );
|
|
}
|
|
|
|
/**
|
|
* Get keywords table.
|
|
*
|
|
* @return \MyThemeShop\Database\Query_Builder
|
|
*/
|
|
public static function keywords() {
|
|
return Database::table( 'rank_math_analytics_keyword_manager' );
|
|
}
|
|
|
|
/**
|
|
* Delete console and analytics data.
|
|
*
|
|
* @param int $days Decide whether to delete all or delete 90 days data.
|
|
*/
|
|
public static function delete_by_days( $days ) {
|
|
if ( -1 === $days ) {
|
|
self::traffic()->truncate();
|
|
self::analytics()->truncate();
|
|
} else {
|
|
$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-1 days' ) );
|
|
$end = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days' ) );
|
|
|
|
self::traffic()->whereBetween( 'created', [ $end, $start ] )->delete();
|
|
self::analytics()->whereBetween( 'created', [ $end, $start ] )->delete();
|
|
}
|
|
self::purge_cache();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Delete record for comparison.
|
|
*/
|
|
public static function delete_data_log() {
|
|
$days = Helper::get_settings( 'general.console_caching_control', 90 );
|
|
|
|
$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . ( $days * 2 ) . ' days' ) );
|
|
|
|
self::traffic()->where( 'created', '<', $start )->delete();
|
|
self::analytics()->where( 'created', '<', $start )->delete();
|
|
}
|
|
|
|
/**
|
|
* Purge SC transient
|
|
*/
|
|
public static function purge_cache() {
|
|
$table = Database::table( 'options' );
|
|
$table->whereLike( 'option_name', 'rank_math_analytics_data_info' )->delete();
|
|
$table->whereLike( 'option_name', 'tracked_keywords_summary' )->delete();
|
|
$table->whereLike( 'option_name', 'top_keywords' )->delete();
|
|
$table->whereLike( 'option_name', 'top_keywords_graph' )->delete();
|
|
$table->whereLike( 'option_name', 'winning_keywords' )->delete();
|
|
$table->whereLike( 'option_name', 'losing_keywords' )->delete();
|
|
$table->whereLike( 'option_name', 'posts_summary' )->delete();
|
|
$table->whereLike( 'option_name', 'winning_posts' )->delete();
|
|
$table->whereLike( 'option_name', 'losing_posts' )->delete();
|
|
|
|
wp_cache_flush();
|
|
}
|
|
|
|
/**
|
|
* Get search console table info (for pro version only).
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function info() {
|
|
global $wpdb;
|
|
|
|
$key = 'rank_math_analytics_data_info';
|
|
$data = get_transient( $key );
|
|
if ( false !== $data ) {
|
|
return $data;
|
|
}
|
|
|
|
$days = 0;
|
|
|
|
$rows = self::get_total_rows();
|
|
|
|
$size = $wpdb->get_var( 'SELECT SUM((data_length + index_length)) AS size FROM information_schema.TABLES WHERE table_schema="' . $wpdb->dbname . '" AND table_name IN ( ' . '"' . $wpdb->prefix . 'rank_math_analytics_ga", "' . $wpdb->prefix . 'rank_math_analytics_adsense"' . ' )' ); // phpcs:ignore
|
|
|
|
$data = compact( 'days', 'rows', 'size' );
|
|
|
|
set_transient( $key, $data, DAY_IN_SECONDS );
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Get total row count of analytics and adsense tables
|
|
*
|
|
* @return int total row count
|
|
*/
|
|
public static function get_total_rows() {
|
|
$rows = 0;
|
|
|
|
if ( Analytics_Free::is_analytics_connected() ) {
|
|
$rows += self::table( 'rank_math_analytics_ga' )
|
|
->selectCount( 'id' )
|
|
->getVar();
|
|
}
|
|
|
|
if ( Adsense::is_adsense_connected() ) {
|
|
$rows += self::table( 'rank_math_analytics_adsense' )
|
|
->selectCount( 'id' )
|
|
->getVar();
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Has data pulled.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function has_data() {
|
|
static $rank_math_gsc_has_data;
|
|
if ( isset( $rank_math_gsc_has_data ) ) {
|
|
return $rank_math_gsc_has_data;
|
|
}
|
|
|
|
$id = self::objects()
|
|
->select( 'id' )
|
|
->limit( 1 )
|
|
->getVar();
|
|
|
|
$rank_math_gsc_has_data = $id > 0 ? true : false;
|
|
return $rank_math_gsc_has_data;
|
|
}
|
|
|
|
/**
|
|
* Add a new record into objects table.
|
|
*
|
|
* @param array $args Values to insert.
|
|
*
|
|
* @return bool|int
|
|
*/
|
|
public static function add_object( $args = [] ) {
|
|
if ( empty( $args ) ) {
|
|
return false;
|
|
}
|
|
|
|
$args = wp_parse_args(
|
|
$args,
|
|
[
|
|
'created' => current_time( 'mysql' ),
|
|
'page' => '',
|
|
'object_type' => 'post',
|
|
'object_subtype' => 'post',
|
|
'object_id' => 0,
|
|
'primary_key' => '',
|
|
'seo_score' => 0,
|
|
'page_score' => 0,
|
|
'is_indexable' => false,
|
|
'schemas_in_use' => '',
|
|
]
|
|
);
|
|
|
|
return self::objects()->insert( $args, [ '%s', '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%d', '%s' ] );
|
|
}
|
|
|
|
/**
|
|
* Add/Update a record into/from objects table.
|
|
*
|
|
* @param array $args Values to update.
|
|
*
|
|
* @return bool|int
|
|
*/
|
|
public static function update_object( $args = [] ) {
|
|
if ( empty( $args ) ) {
|
|
return false;
|
|
}
|
|
|
|
// If object exists, try to update.
|
|
$old_id = absint( $args['id'] );
|
|
if ( ! empty( $old_id ) ) {
|
|
unset( $args['id'] );
|
|
|
|
$updated = self::objects()->set( $args )
|
|
->where( 'id', $old_id )
|
|
->where( 'object_id', absint( $args['object_id'] ) )
|
|
->update();
|
|
|
|
if ( ! empty( $updated ) ) {
|
|
return $old_id;
|
|
}
|
|
}
|
|
|
|
// In case of new object or failed to update, try to add.
|
|
return self::add_object( $args );
|
|
}
|
|
|
|
/**
|
|
* Add console records.
|
|
*
|
|
* @param string $date Date of creation.
|
|
* @param array $rows Data rows to insert.
|
|
*/
|
|
public static function add_query_page_bulk( $date, $rows ) {
|
|
$chunks = array_chunk( $rows, 50 );
|
|
|
|
foreach ( $chunks as $chunk ) {
|
|
self::bulk_insert_query_page_data( $date . ' 00:00:00', $chunk );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bulk inserts records into a table using WPDB. All rows must contain the same keys.
|
|
*
|
|
* @param string $date Date.
|
|
* @param array $rows Rows to insert.
|
|
*/
|
|
public static function bulk_insert_query_page_data( $date, $rows ) {
|
|
global $wpdb;
|
|
|
|
$data = [];
|
|
$placeholders = [];
|
|
$columns = [
|
|
'created',
|
|
'query',
|
|
'page',
|
|
'clicks',
|
|
'impressions',
|
|
'position',
|
|
'ctr',
|
|
];
|
|
$columns = '`' . implode( '`, `', $columns ) . '`';
|
|
$placeholder = [
|
|
'%s',
|
|
'%s',
|
|
'%s',
|
|
'%d',
|
|
'%d',
|
|
'%d',
|
|
'%d',
|
|
];
|
|
|
|
// Start building SQL, initialise data and placeholder arrays.
|
|
$sql = "INSERT INTO `{$wpdb->prefix}rank_math_analytics_gsc` ( $columns ) VALUES\n";
|
|
|
|
// Build placeholders for each row, and add values to data array.
|
|
foreach ( $rows as $row ) {
|
|
if (
|
|
$row['position'] > self::get_position_filter() ||
|
|
Str::contains( '?', $row['page'] )
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
$data[] = $date;
|
|
$data[] = $row['query'];
|
|
$data[] = Stats::get_relative_url( self::remove_hash( $row['page'] ) );
|
|
$data[] = $row['clicks'];
|
|
$data[] = $row['impressions'];
|
|
$data[] = $row['position'];
|
|
$data[] = $row['ctr'];
|
|
|
|
$placeholders[] = '(' . implode( ', ', $placeholder ) . ')';
|
|
}
|
|
|
|
// Don't run insert with empty dataset, return 0 since no rows affected.
|
|
if ( empty( $data ) ) {
|
|
return 0;
|
|
}
|
|
|
|
// Stitch all rows together.
|
|
$sql .= implode( ",\n", $placeholders );
|
|
|
|
// Run the query. Returns number of affected rows.
|
|
return $wpdb->query( $wpdb->prepare( $sql, $data ) ); // phpcs:ignore
|
|
}
|
|
|
|
/**
|
|
* Add analytic records.
|
|
*
|
|
* @param string $date Date of creation.
|
|
* @param array $rows Data rows to insert.
|
|
*/
|
|
public static function add_analytics_bulk( $date, $rows ) {
|
|
$chunks = array_chunk( $rows, 50 );
|
|
|
|
foreach ( $chunks as $chunk ) {
|
|
self::bulk_insert_analytics_data( $date . ' 00:00:00', $chunk );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bulk inserts records into a table using WPDB. All rows must contain the same keys.
|
|
*
|
|
* @param string $date Date.
|
|
* @param array $rows Rows to insert.
|
|
*/
|
|
public static function bulk_insert_analytics_data( $date, $rows ) {
|
|
global $wpdb;
|
|
|
|
$data = [];
|
|
$placeholders = [];
|
|
$columns = [
|
|
'created',
|
|
'page',
|
|
'pageviews',
|
|
'visitors',
|
|
];
|
|
$columns = '`' . implode( '`, `', $columns ) . '`';
|
|
$placeholder = [
|
|
'%s',
|
|
'%s',
|
|
'%d',
|
|
'%d',
|
|
];
|
|
|
|
// Start building SQL, initialise data and placeholder arrays.
|
|
$sql = "INSERT INTO `{$wpdb->prefix}rank_math_analytics_ga` ( $columns ) VALUES\n";
|
|
|
|
// Build placeholders for each row, and add values to data array.
|
|
foreach ( $rows as $row ) {
|
|
$page = '';
|
|
$pageviews = '';
|
|
$visitors = '';
|
|
|
|
if ( ! isset( $row['dimensionValues'] ) ) {
|
|
if ( empty( $row['dimensions'][1] ) || Str::contains( '?', $row['dimensions'][1] ) ) {
|
|
continue;
|
|
}
|
|
$page = $row['dimensions'][2] . $row['dimensions'][1];
|
|
$pageviews = $row['metrics'][0]['values'][0];
|
|
$visitors = $row['metrics'][0]['values'][1];
|
|
} else {
|
|
if ( empty( $row['dimensionValues'][1]['value'] ) || Str::contains( '?', $row['dimensionValues'][1]['value'] ) ) {
|
|
continue;
|
|
}
|
|
$page = $row['dimensionValues'][0]['value'] . $row['dimensionValues'][1]['value'];
|
|
$pageviews = $row['metricValues'][0]['value'];
|
|
$visitors = $row['metricValues'][1]['value'];
|
|
}
|
|
|
|
if ( $page && $pageviews && $visitors ) {
|
|
$page = ( is_ssl() ? 'https' : 'http' ) . '://' . $page;
|
|
|
|
$data[] = $date;
|
|
$data[] = Stats::get_relative_url( self::remove_hash( $page ) );
|
|
$data[] = $pageviews;
|
|
$data[] = $visitors;
|
|
|
|
$placeholders[] = '(' . implode( ', ', $placeholder ) . ')';
|
|
}
|
|
}
|
|
|
|
if ( empty( $placeholders ) ) {
|
|
return 0;
|
|
}
|
|
|
|
// Stitch all rows together.
|
|
$sql .= implode( ",\n", $placeholders );
|
|
|
|
// Run the query. Returns number of affected rows.
|
|
return $wpdb->query( $wpdb->prepare( $sql, $data ) ); // phpcs:ignore
|
|
}
|
|
|
|
/**
|
|
* Remove hash part from Url.
|
|
*
|
|
* @param string $url Url to process.
|
|
* @return string
|
|
*/
|
|
public static function remove_hash( $url ) {
|
|
if ( ! Str::contains( '#', $url ) ) {
|
|
return $url;
|
|
}
|
|
|
|
$url = \explode( '#', $url );
|
|
return $url[0];
|
|
}
|
|
|
|
/**
|
|
* Add adsense records.
|
|
*
|
|
* @param array $rows Data rows to insert.
|
|
*/
|
|
public static function add_adsense( $rows ) {
|
|
if ( ! \MyThemeShop\Helpers\DB::check_table_exists( 'rank_math_analytics_adsense' ) ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $rows as $row ) {
|
|
$date = $row['cells'][0]['value'];
|
|
$earnings = floatval( $row['cells'][1]['value'] );
|
|
|
|
self::adsense()
|
|
->insert(
|
|
[
|
|
'created' => $date . ' 00:00:00',
|
|
'earnings' => $earnings,
|
|
],
|
|
[ '%s', '%f' ]
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get position filter.
|
|
*
|
|
* @return int
|
|
*/
|
|
private static function get_position_filter() {
|
|
$number = apply_filters( 'rank_math/analytics/position_limit', false );
|
|
if ( false === $number ) {
|
|
return 100;
|
|
}
|
|
|
|
return $number;
|
|
}
|
|
|
|
/**
|
|
* Bulk inserts records into a keyword table using WPDB. All rows must contain the same keys.
|
|
*
|
|
* @param array $rows Rows to insert.
|
|
*/
|
|
public static function bulk_insert_query_focus_keyword_data( $rows ) {
|
|
$registered = Admin_Helper::get_registration_data();
|
|
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Check remain keywords count can be added.
|
|
$total_keywords = Keywords::get()->get_tracked_keywords_count();
|
|
$new_keywords = Keywords::get()->extract_addable_track_keyword( implode( ',', $rows ) );
|
|
$keywords_count = count( $new_keywords );
|
|
if ( $keywords_count <= 0 ) {
|
|
return false;
|
|
}
|
|
|
|
$summary = Keywords::get()->get_tracked_keywords_quota();
|
|
$remain = $summary['available'] - $total_keywords;
|
|
if ( $remain <= 0 ) {
|
|
return false;
|
|
}
|
|
|
|
// Add remaining limit keywords.
|
|
$new_keywords = array_slice( $new_keywords, 0, $remain );
|
|
|
|
$data = [];
|
|
$placeholders = [];
|
|
$columns = [
|
|
'keyword',
|
|
'collection',
|
|
'is_active',
|
|
];
|
|
$columns = '`' . implode( '`, `', $columns ) . '`';
|
|
$placeholder = [
|
|
'%s',
|
|
'%s',
|
|
'%s',
|
|
];
|
|
|
|
// Start building SQL, initialise data and placeholder arrays.
|
|
global $wpdb;
|
|
$sql = "INSERT INTO `{$wpdb->prefix}rank_math_analytics_keyword_manager` ( $columns ) VALUES\n";
|
|
|
|
// Build placeholders for each row, and add values to data array.
|
|
foreach ( $new_keywords as $new_keyword ) {
|
|
$data[] = $new_keyword;
|
|
$data[] = 'uncategorized';
|
|
$data[] = 1;
|
|
$placeholders[] = '(' . implode( ', ', $placeholder ) . ')';
|
|
}
|
|
|
|
// Stitch all rows together.
|
|
$sql .= implode( ",\n", $placeholders );
|
|
|
|
// Run the query. Returns number of affected rows.
|
|
$count = $wpdb->query( $wpdb->prepare( $sql, $data ) ); // phpcs:ignore
|
|
|
|
$total_keywords = Keywords::get()->get_tracked_keywords_count();
|
|
$response = \RankMathPro\Admin\Api::get()->keywords_info( $registered['username'], $registered['api_key'], $total_keywords );
|
|
if ( $response ) {
|
|
update_option( 'rank_math_keyword_quota', $response );
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get stats from DB for "Presence on Google" widget:
|
|
* All unique coverage_state values and their counts.
|
|
*/
|
|
public static function get_presence_stats() {
|
|
$results = self::inspections()
|
|
->select(
|
|
[
|
|
'coverage_state',
|
|
'COUNT(*)' => 'count',
|
|
]
|
|
)
|
|
->groupBy( 'coverage_state' )
|
|
->orderBy( 'count', 'DESC' )
|
|
->get();
|
|
|
|
$results = array_map(
|
|
'absint',
|
|
array_combine(
|
|
array_column( $results, 'coverage_state' ),
|
|
array_column( $results, 'count' )
|
|
)
|
|
);
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get stats from DB for "Top Statuses" widget.
|
|
*/
|
|
public static function get_status_stats() {
|
|
$statuses = [
|
|
'VERDICT_UNSPECIFIED',
|
|
'PASS',
|
|
'PARTIAL',
|
|
'FAIL',
|
|
'NEUTRAL',
|
|
];
|
|
|
|
// Get all unique index_verdict values and their counts.
|
|
$index_statuses = self::inspections()
|
|
->select(
|
|
[
|
|
'index_verdict',
|
|
'COUNT(*)' => 'count',
|
|
]
|
|
)
|
|
->groupBy( 'index_verdict' )
|
|
->orderBy( 'count', 'DESC' )
|
|
->get();
|
|
|
|
$results = array_fill_keys( $statuses, 0 );
|
|
foreach ( $index_statuses as $status ) {
|
|
if ( empty( $status->index_verdict ) ) {
|
|
continue;
|
|
}
|
|
|
|
$results[ $status->index_verdict ] += $status->count;
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get stats from DB for "Top Statuses" widget.
|
|
*/
|
|
public static function get_index_verdict( $page ) {
|
|
$verdict = self::inspections()
|
|
->select()
|
|
->where( 'page', '=', $page )
|
|
->one();
|
|
|
|
return (array) $verdict;
|
|
}
|
|
}
|