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.
495 lines
12 KiB
PHP
495 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* The Analytics module database operations
|
|
*
|
|
* @since 1.0.49
|
|
* @package RankMath
|
|
* @subpackage RankMath\modules
|
|
* @author Rank Math <support@rankmath.com>
|
|
*/
|
|
|
|
namespace RankMath\Analytics;
|
|
|
|
use RankMath\Helper;
|
|
use RankMath\Google\Api;
|
|
use RankMath\Google\Console;
|
|
use RankMath\Helpers\Str;
|
|
use RankMath\Helpers\DB as DB_Helper;
|
|
use RankMath\Admin\Database\Database;
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* DB class.
|
|
*/
|
|
class DB {
|
|
|
|
/**
|
|
* Get any table.
|
|
*
|
|
* @param string $table_name Table name.
|
|
*
|
|
* @return \RankMath\Admin\Database\Query_Builder
|
|
*/
|
|
public static function table( $table_name ) {
|
|
return Database::table( $table_name );
|
|
}
|
|
|
|
/**
|
|
* Get console data table.
|
|
*
|
|
* @return \RankMath\Admin\Database\Query_Builder
|
|
*/
|
|
public static function analytics() {
|
|
return Database::table( 'rank_math_analytics_gsc' );
|
|
}
|
|
|
|
/**
|
|
* Get objects table.
|
|
*
|
|
* @return \RankMath\Admin\Database\Query_Builder
|
|
*/
|
|
public static function objects() {
|
|
return Database::table( 'rank_math_analytics_objects' );
|
|
}
|
|
|
|
/**
|
|
* Get inspections table.
|
|
*
|
|
* @return \RankMath\Admin\Database\Query_Builder
|
|
*/
|
|
public static function inspections() {
|
|
return Database::table( 'rank_math_analytics_inspections' );
|
|
}
|
|
|
|
/**
|
|
* Delete a record.
|
|
*
|
|
* @param int $days Decide whether to delete all or delete 90 days data.
|
|
*/
|
|
public static function delete_by_days( $days ) {
|
|
// Delete console data.
|
|
if ( Console::is_console_connected() ) {
|
|
if ( -1 === $days ) {
|
|
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::analytics()->whereBetween( 'created', [ $end, $start ] )->delete();
|
|
}
|
|
}
|
|
|
|
// Delete analytics, adsense data.
|
|
do_action( 'rank_math/analytics/delete_by_days', $days );
|
|
self::purge_cache();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Delete record for comparison.
|
|
*/
|
|
public static function delete_data_log() {
|
|
$days = Helper::get_settings( 'general.console_caching_control', 90 );
|
|
|
|
// Delete old console data more than 2 times ago of specified number of days to keep the data.
|
|
$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . ( $days * 2 ) . ' days' ) );
|
|
|
|
self::analytics()->where( 'created', '<', $start )->delete();
|
|
|
|
// Delete old analytics and adsense data.
|
|
do_action( 'rank_math/analytics/delete_data_log', $start );
|
|
}
|
|
|
|
/**
|
|
* Purge SC transient
|
|
*/
|
|
public static function purge_cache() {
|
|
$table = Database::table( 'options' );
|
|
$table->whereLike( 'option_name', 'top_keywords' )->delete();
|
|
$table->whereLike( 'option_name', 'posts_summary' )->delete();
|
|
$table->whereLike( 'option_name', 'top_keywords_graph' )->delete();
|
|
$table->whereLike( 'option_name', 'dashboard_stats_widget' )->delete();
|
|
$table->whereLike( 'option_name', 'rank_math_analytics_data_info' )->delete();
|
|
|
|
do_action( 'rank_math/analytics/purge_cache', $table );
|
|
|
|
wp_cache_flush();
|
|
}
|
|
|
|
/**
|
|
* Get search console table info.
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function info() {
|
|
global $wpdb;
|
|
|
|
if ( ! Api::get()->is_console_connected() ) {
|
|
return [];
|
|
}
|
|
|
|
$key = 'rank_math_analytics_data_info';
|
|
$data = get_transient( $key );
|
|
if ( false !== $data ) {
|
|
return $data;
|
|
}
|
|
|
|
$days = self::analytics()
|
|
->selectCount( 'DISTINCT(created)', 'days' )
|
|
->getVar();
|
|
|
|
$rows = self::analytics()
|
|
->selectCount( 'id' )
|
|
->getVar();
|
|
|
|
$size = $wpdb->get_var( "SELECT SUM((data_length + index_length)) AS size FROM information_schema.TABLES WHERE table_schema='" . $wpdb->dbname . "' AND (table_name='" . $wpdb->prefix . "rank_math_analytics_gsc')" ); // phpcs:ignore
|
|
$data = compact( 'days', 'rows', 'size' );
|
|
|
|
$data = apply_filters( 'rank_math/analytics/analytics_tables_info', $data );
|
|
|
|
set_transient( $key, $data, DAY_IN_SECONDS );
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Check if console data exists at specified date.
|
|
*
|
|
* @param string $date Date to check data existence.
|
|
* @param string $action Action name to filter data type.
|
|
* @return boolean
|
|
*/
|
|
public static function date_exists( $date, $action = 'console' ) {
|
|
$tables['console'] = DB_Helper::check_table_exists( 'rank_math_analytics_gsc' ) ? 'rank_math_analytics_gsc' : '';
|
|
|
|
/**
|
|
* Filter: 'rank_math/analytics/date_exists_tables' - Allow developers to add more tables to check.
|
|
*/
|
|
$tables = apply_filters( 'rank_math/analytics/date_exists_tables', $tables, $date, $action );
|
|
|
|
if ( empty( $tables[ $action ] ) ) {
|
|
return true; // Should return true to avoid further data fetch action.
|
|
}
|
|
|
|
$table = self::table( $tables[ $action ] );
|
|
|
|
$id = $table
|
|
->select( 'id' )
|
|
->where( 'DATE(created)', $date )
|
|
->getVar();
|
|
|
|
return $id > 0 ? true : false;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
unset( $args['id'] );
|
|
|
|
$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 new record in the inspections table.
|
|
*
|
|
* @param array $args Values to insert.
|
|
*
|
|
* @return bool|int
|
|
*/
|
|
public static function store_inspection( $args = [] ) {
|
|
if ( empty( $args ) || empty( $args['page'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
unset( $args['id'] );
|
|
|
|
$defaults = self::get_inspection_defaults();
|
|
|
|
// Only keep $args items that are in $defaults.
|
|
$args = array_intersect_key( $args, $defaults );
|
|
|
|
// Apply defaults.
|
|
$args = wp_parse_args( $args, $defaults );
|
|
|
|
// We only have strings: placeholders will be '%s'.
|
|
$format = array_fill( 0, count( $args ), '%s' );
|
|
|
|
// Check if we have an existing record, based on 'page'.
|
|
$id = self::inspections()
|
|
->select( 'id' )
|
|
->where( 'page', $args['page'] )
|
|
->getVar();
|
|
|
|
if ( $id ) {
|
|
return self::inspections()
|
|
->set( $args )
|
|
->where( 'id', $id )
|
|
->update();
|
|
}
|
|
|
|
return self::inspections()->insert( $args, $format );
|
|
}
|
|
|
|
/**
|
|
* Get inspection defaults.
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function get_inspection_defaults() {
|
|
$defaults = [
|
|
'created' => current_time( 'mysql' ),
|
|
'page' => '',
|
|
'index_verdict' => 'VERDICT_UNSPECIFIED',
|
|
'indexing_state' => 'INDEXING_STATE_UNSPECIFIED',
|
|
'coverage_state' => '',
|
|
'page_fetch_state' => 'PAGE_FETCH_STATE_UNSPECIFIED',
|
|
'robots_txt_state' => 'ROBOTS_TXT_STATE_UNSPECIFIED',
|
|
'mobile_usability_verdict' => 'VERDICT_UNSPECIFIED',
|
|
'mobile_usability_issues' => '',
|
|
'rich_results_verdict' => 'VERDICT_UNSPECIFIED',
|
|
'rich_results_items' => '',
|
|
'last_crawl_time' => '',
|
|
'crawled_as' => 'CRAWLING_USER_AGENT_UNSPECIFIED',
|
|
'google_canonical' => '',
|
|
'user_canonical' => '',
|
|
'sitemap' => '',
|
|
'referring_urls' => '',
|
|
'raw_api_response' => '',
|
|
];
|
|
|
|
return apply_filters( 'rank_math/analytics/inspection_defaults', $defaults );
|
|
}
|
|
|
|
/**
|
|
* 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 console 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[] = str_replace( Helper::get_home_url(), '', self::remove_hash( urldecode( $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
|
|
}
|
|
|
|
/**
|
|
* 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];
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Get all inspections.
|
|
*
|
|
* @param array $params REST Parameters.
|
|
* @param int $per_page Limit.
|
|
*/
|
|
public static function get_inspections( $params, $per_page ) {
|
|
$page = ! empty( $params['page'] ) ? absint( $params['page'] ) : 1;
|
|
$per_page = absint( $per_page );
|
|
$offset = ( $page - 1 ) * $per_page;
|
|
|
|
$inspections = self::inspections()->table;
|
|
$objects = self::objects()->table;
|
|
|
|
$query = self::inspections()
|
|
->select( [ "$inspections.*", "$objects.title", "$objects.object_id" ] )
|
|
->leftJoin( $objects, "$inspections.page", "$objects.page" )
|
|
->where( "$objects.page", '!=', '' )
|
|
->orderBy( 'id', 'DESC' )
|
|
->limit( $per_page, $offset );
|
|
|
|
do_action_ref_array( 'rank_math/analytics/get_inspections_query', [ &$query, $params ] );
|
|
|
|
$results = $query->get();
|
|
|
|
return apply_filters( 'rank_math/analytics/get_inspections_results', $results );
|
|
}
|
|
|
|
/**
|
|
* Get inspections count.
|
|
*
|
|
* @param array $params REST Parameters.
|
|
*
|
|
* @return int
|
|
*/
|
|
public static function get_inspections_count( $params ) {
|
|
$pages = self::objects()->select( 'page' )->get( ARRAY_A );
|
|
$pages = array_unique( wp_list_pluck( $pages, 'page' ) );
|
|
$query = self::inspections()->selectCount( 'id', 'total' )->whereIn( 'page', $pages );
|
|
|
|
do_action_ref_array( 'rank_math/analytics/get_inspections_count_query', [ &$query, $params ] );
|
|
|
|
return $query->getVar();
|
|
}
|
|
}
|