*/ 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; } }