Commit realizado el 12:13:52 08-04-2024

This commit is contained in:
Pagina Web Monito
2024-04-08 12:13:55 -04:00
commit 0c33094de9
7815 changed files with 1365694 additions and 0 deletions

View File

@@ -0,0 +1 @@
:root{--rankmath-wp-adminbar-height: 0}@keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(-360deg)}}@keyframes bounce{from{transform:translateY(0px)}to{transform:translateY(-5px)}}.rank-math-pagespeed-box .rank-math-pagespeed-header{display:flex;flex-flow:row wrap}.rank-math-pagespeed-box .rank-math-pagespeed-header{align-items:center}.rank-math-pagespeed-box .rank-math-pagespeed-header h3{font-size:1rem}.rank-math-pagespeed-box .rank-math-pagespeed-header span{margin-left:auto}.rank-math-pagespeed-box .rank-math-pagespeed-header .button{line-height:1;width:30px;height:30px;padding:0;text-align:center;text-decoration:none;color:#b5bfc9;border:0;fill:#b5bfc9;-webkit-text-stroke:1px white}.rank-math-pagespeed-box .rank-math-pagespeed-header .button svg,.rank-math-pagespeed-box .rank-math-pagespeed-header .button .dashicon{margin-right:0;vertical-align:-2px}.rank-math-pagespeed-box .rank-math-pagespeed-header .button:hover,.rank-math-pagespeed-box .rank-math-pagespeed-header .button:focus,.rank-math-pagespeed-box .rank-math-pagespeed-header .button:active{outline:none;background:transparent;box-shadow:none;fill:#069de3}.rank-math-pagespeed-box .rank-math-pagespeed-header .button.loading{display:inline-block;animation:rm-spin 1s linear infinite}@keyframes rm-spin{0%{transform:rotate(360deg);color:#10AC84;fill:#10AC84}25%{color:#4e8cde;fill:#4e8cde}50%{color:#ed5e5e;fill:#ed5e5e}75%{color:#FF9F43;fill:#FF9F43}100%{transform:rotate(0deg);color:#F368E0;fill:#F368E0}}.rank-math-pagespeed-box .col{margin-top:.825rem;flex:0 0 50%}.rank-math-pagespeed-box .col .rm-icon{font-size:1.25rem;margin-right:8px;vertical-align:middle}.rank-math-pagespeed-box .col strong{font-size:1rem;font-weight:500;margin-right:10px;vertical-align:middle}.rank-math-pagespeed-box .col strong.interactive-good{color:#52a652}.rank-math-pagespeed-box .col strong.interactive-fair{color:#FF9800}.rank-math-pagespeed-box .col strong.interactive-bad{color:#f0776f}.rank-math-pagespeed-box .col small{font-size:14px;font-weight:500;line-height:24px;display:inline-block;width:42px;text-align:center;color:#52a652;border-radius:5px;background:#e9f4e9}.rank-math-pagespeed-box .col small.score-fair{color:#FF9800;background:#ffefd6}.rank-math-pagespeed-box .col small.score-bad{color:#f0776f;background:#fdedec}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,96 @@
<?php
/**
* The Analytics AJAX
*
* @since 1.4.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMathPro\Google\Adsense;
use MyThemeShop\Helpers\Param;
use RankMath\Analytics\Workflow\Base;
use RankMath\Analytics\Workflow\Workflow;
defined( 'ABSPATH' ) || exit;
/**
* Ajax class.
*/
class Ajax {
use \RankMath\Traits\Ajax;
/**
* The Constructor
*/
public function __construct() {
$this->ajax( 'save_adsense_account', 'save_adsense_account' );
$this->ajax( 'check_adsense_request', 'check_adsense_request' );
}
/**
* Check the Google AdSense request.
*/
public function check_adsense_request() {
check_ajax_referer( 'rank-math-ajax-nonce', 'security' );
$this->has_cap_ajax( 'analytics' );
$dates = Base::get_dates();
$success = Adsense::get_adsense(
[
'start_date' => $dates['start_date'],
'end_date' => $dates['end_date'],
]
);
if ( is_wp_error( $success ) ) {
$this->error( esc_html__( 'Data import will not work for this service as sufficient permissions are not given.', 'rank-math-pro' ) );
}
$this->success();
}
/**
* Save adsense profile.
*/
public function save_adsense_account() {
$this->verify_nonce( 'rank-math-ajax-nonce' );
$this->has_cap_ajax( 'analytics' );
$prev = get_option( 'rank_math_google_analytic_options', [] );
$value = get_option( 'rank_math_google_analytic_options', [] );
$value['adsense_id'] = Param::post( 'accountID' );
// Test AdSense connection request.
if ( ! empty( $value['adsense_id'] ) ) {
$dates = Base::get_dates();
$request = Adsense::get_adsense(
[
'account_id' => $value['adsense_id'],
'start_date' => $dates['start_date'],
'end_date' => $dates['end_date'],
]
);
if ( is_wp_error( $request ) ) {
$this->error( esc_html__( 'Data import will not work for this service as sufficient permissions are not given.', 'rank-math' ) );
}
}
update_option( 'rank_math_google_analytic_options', $value );
$days = Param::get( 'days', 90, FILTER_VALIDATE_INT );
Workflow::do_workflow(
'adsense',
$days,
$prev,
$value
);
$this->success();
}
}

View File

@@ -0,0 +1,675 @@
<?php
/**
* Analytics module.
*
* @since 1.0
* @package RankMathPro
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use MyThemeShop\Helpers\Param;
use MyThemeShop\Helpers\DB as DB_Helper;
// Analytics.
use RankMathPro\Google\Adsense;
use RankMath\Google\Permissions;
use RankMath\Google\Authentication;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Analytics\Workflow\Jobs;
use RankMathPro\Analytics\Workflow\Workflow;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
use RankMathPro\Analytics\DB;
defined( 'ABSPATH' ) || exit;
/**
* Analytics class.
*/
class Analytics {
use Hooker;
/**
* Constructor.
*/
public function __construct() {
$this->action( 'rank_math/admin/enqueue_scripts', 'enqueue_analytics' );
$this->action( 'rank_math/analytics/options/console', 'add_country_dropdown3' );
$this->action( 'rank_math/analytics/options/analytics', 'add_country_dropdown2' );
$this->action( 'update_option_rank_math_analytics_last_updated', 'send_summary' );
$this->action( 'rank_math/admin/settings/analytics', 'add_new_settings' );
$this->filter( 'rank_math/analytics/schedule_gap', 'schedule_gap' );
$this->filter( 'rank_math/analytics/fetch_gap', 'fetch_gap' );
$this->filter( 'rank_math/analytics/max_days_allowed', 'data_retention_period' );
$this->filter( 'rank_math/analytics/options/cache_control/description', 'change_description' );
$this->filter( 'rank_math/analytics/check_all_services', 'check_all_services' );
$this->filter( 'rank_math/analytics/user_preference', 'change_user_preference' );
$this->action( 'template_redirect', 'local_js_endpoint' );
$this->filter( 'rank_math/analytics/gtag_config', 'gtag_config' );
$this->filter( 'rank_math/status/rank_math_info', 'google_permission_info' );
$this->filter( 'rank_math/analytics/gtag', 'gtag' );
$this->filter( 'rank_math/analytics/pre_filter_data', 'filter_winning_losing_posts', 10, 3 );
$this->filter( 'rank_math/analytics/pre_filter_data', 'filter_winning_keywords', 10, 3 );
$this->action( 'cmb2_save_options-page_fields_rank-math-options-general_options', 'sync_global_settings', 25, 2 );
$this->filter( 'rank_math/metabox/post/values', 'add_metadata', 10, 2 );
$this->filter( 'rank_math/analytics/date_exists_tables', 'date_exists_tables', 10 );
if ( Helper::has_cap( 'analytics' ) ) {
$this->action( 'rank_math/admin_bar/items', 'admin_bar_items', 11 );
$this->action( 'rank_math_seo_details', 'post_column_search_traffic' );
}
if ( Helper::can_add_frontend_stats() ) {
$this->action( 'wp_enqueue_scripts', 'enqueue' );
}
Posts::get();
Keywords::get();
Jobs::get();
Workflow::get();
new Pageviews();
new Summary();
new Ajax();
new Email_Reports();
new Url_Inspection();
}
/**
* Enqueue Frontend stats script.
*/
public function enqueue() {
if ( ! is_singular() || is_admin() || is_preview() || Helper::is_divi_frontend_editor() ) {
return;
}
$uri = untrailingslashit( plugin_dir_url( __FILE__ ) );
wp_enqueue_style( 'rank-math-analytics-pro-stats', $uri . '/assets/css/admin-bar.css', [ 'rank-math-analytics-stats' ], rank_math_pro()->version );
wp_enqueue_script( 'rank-math-analytics-pro-stats', $uri . '/assets/js/admin-bar.js', [ 'rank-math-analytics-stats' ], rank_math_pro()->version, true );
Helper::add_json( 'dateFormat', get_option( 'date_format' ) );
}
/**
* Add localized data to use in the Post Editor.
*
* @param array $values Aray of localized data.
*
* @return array
*/
public function add_metadata( $values ) {
$values['isAnalyticsConnected'] = \RankMath\Google\Analytics::is_analytics_connected();
return $values;
}
/**
* Change user perference.
*
* @param array $preference Array of preference.
* @return array
*/
public function change_user_preference( $preference ) {
Helper::add_json( 'isAdsenseConnected', ! empty( Adsense::get_adsense_id() ) );
Helper::add_json( 'isLinkModuleActive', Helper::is_module_active( 'link-counter' ) );
Helper::add_json( 'isSchemaModuleActive', Helper::is_module_active( 'rich-snippet' ) );
Helper::add_json( 'isAnalyticsConnected', \RankMath\Google\Analytics::is_analytics_connected() );
Helper::add_json( 'dateFormat', get_option( 'date_format' ) );
$preference['topKeywords']['ctr'] = false;
$preference['topKeywords']['ctr'] = false;
$preference['performance']['clicks'] = false;
return $preference;
}
/**
* Data rentention days.
*
* @return int
*/
public function data_retention_period() {
return 'pro' === Admin_Helper::get_user_plan() ? 180 : 1000;
}
/**
* Data retrival job gap in seconds.
*
* @return int
*/
public function schedule_gap() {
return 10;
}
/**
* Data retrival fetch gap in days.
*
* @return int
*/
public function fetch_gap() {
return 3;
}
/**
* Fetch adsense account.
*
* @param array $result Result array.
* @return array
*/
public function check_all_services( $result ) {
$result['adsenseAccounts'] = Adsense::get_adsense_accounts();
return $result;
}
/**
* Add admin bar item.
*
* @param Admin_Bar_Menu $menu Menu class instance.
*/
public function admin_bar_items( $menu ) {
$post_types = Helper::get_accessible_post_types();
unset( $post_types['attachment'] );
if ( is_singular( $post_types ) && Helper::is_post_indexable( get_the_ID() ) ) {
$menu->add_sub_menu(
'post_analytics',
[
'title' => esc_html__( 'Post Analytics', 'rank-math-pro' ),
'href' => Helper::get_admin_url( 'analytics#/single/' . get_the_ID() ),
'meta' => [ 'title' => esc_html__( 'Analytics Report', 'rank-math-pro' ) ],
'priority' => 20,
]
);
}
}
/**
* Enqueue scripts for the metabox.
*/
public function enqueue_analytics() {
$screen = get_current_screen();
if ( 'rank-math_page_rank-math-analytics' !== $screen->id ) {
return;
}
$url = RANK_MATH_PRO_URL . 'includes/modules/analytics/assets/';
wp_enqueue_style(
'rank-math-pro-analytics',
$url . 'css/stats.css',
null,
rank_math_pro()->version
);
wp_enqueue_script(
'rank-math-pro-analytics',
$url . 'js/stats.js',
[
'wp-components',
'wp-element',
'wp-i18n',
'wp-date',
'wp-html-entities',
'wp-api-fetch',
'rank-math-analytics',
],
rank_math_pro()->version,
true
);
}
/**
* Add country dropdown.
*/
public function add_country_dropdown3() {
$profile = wp_parse_args(
get_option( 'rank_math_google_analytic_profile' ),
[
'profile' => '',
'country' => 'all',
]
);
?>
<div class="cmb-row-col">
<label for="site-console-country"><?php esc_html_e( 'Country', 'rank-math-pro' ); ?></label>
<select class="cmb2_select site-console-country notrack" name="site-console-country" id="site-console-country" disabled="disabled">
<?php foreach ( ProAdminHelper::choices_countries_3() as $code => $label ) : ?>
<option value="<?php echo esc_attr( $code ); ?>"<?php selected( $profile['country'], $code ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php
}
/**
* Add country dropdown.
*/
public function add_country_dropdown2() {
$analytics = $this->get_settings();
?>
<div class="cmb-row-col country-option">
<label for="site-analytics-country"><?php esc_html_e( 'Country', 'rank-math-pro' ); ?></label>
<select class="cmb2_select site-analytics-country notrack" name="site-analytics-country" id="site-analytics-country" disabled="disabled">
<?php foreach ( ProAdminHelper::choices_countries() as $code => $label ) : ?>
<option value="<?php echo esc_attr( $code ); ?>"<?php selected( $analytics['country'], $code ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php
}
/**
* Get Analytics settings.
*
* @return array
*/
public function get_settings() {
return wp_parse_args(
get_option( 'rank_math_google_analytic_options' ),
[
'adsense_id' => '',
'account_id' => '',
'property_id' => '',
'view_id' => '',
'country' => 'all',
'install_code' => false,
'anonymize_ip' => false,
'local_ga_js' => false,
'exclude_loggedin' => false,
]
);
}
/**
* Send analytics summary to RankMath.com.
*/
public function send_summary() {
if ( ! Helper::get_settings( 'general.sync_global_setting' ) ) {
return;
}
$registered = Admin_Helper::get_registration_data();
if ( $registered && isset( $registered['username'] ) && isset( $registered['api_key'] ) ) {
Stats::get()->set_date_range( '-30 days' );
$stats = Stats::get()->get_analytics_summary();
\RankMathPro\Admin\Api::get()->send_summary(
[
'username' => $registered['username'],
'api_key' => $registered['api_key'],
'site_url' => esc_url( home_url() ),
'impressions' => array_values( $stats['impressions'] ),
'clicks' => array_values( $stats['clicks'] ),
'keywords' => array_values( $stats['keywords'] ),
'pageviews' => isset( $stats['pageviews'] ) && is_array( $stats['pageviews'] ) ? array_values( $stats['pageviews'] ) : [],
'adsense' => isset( $stats['adsense'] ) && is_array( $stats['adsense'] ) ? array_values( $stats['adsense'] ) : [],
]
);
}
}
/**
* Change option description.
*/
public function change_description() {
return __( 'Enter the number of days to keep Analytics data in your database. The maximum allowed days are 180. Though, 2x data will be stored in the DB for calculating the difference properly.', 'rank-math-pro' );
}
/**
* Add new settings.
*
* @param object $cmb CMB2 instance.
*/
public function add_new_settings( $cmb ) {
if ( ! Authentication::is_authorized() ) {
return;
}
$type = ! ProAdminHelper::is_business_plan() ? 'hidden' : 'toggle';
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
$fields_position = array_search( 'console_caching_control', array_keys( $field_ids ), true ) + 1;
$cmb->add_field(
[
'id' => 'sync_global_setting',
'type' => $type,
'name' => esc_html__( 'Monitor SEO Performance', 'rank-math-pro' ),
'desc' => sprintf(
/* translators: Link to kb article */
wp_kses_post( __( 'This option allows you to monitor the SEO performance of all of your sites in one centralized dashboard on RankMath.com, so you can check up on sites at a glance. <a href="%1$s" target="_blank">Learn more</a>.', 'rank-math-pro' ) ),
KB::get( 'help-analytics', 'Options Panel Analytics Tab Monitor Performance' )
),
'default' => 'off',
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'google_updates',
'type' => $type,
'name' => esc_html__( 'Google Core Updates in the Graphs', 'rank-math-pro' ),
'desc' => sprintf(
/* translators: Link to kb article */
__( 'This option allows you to show %s in the Analytics graphs.', 'rank-math-pro' ),
'<a href="' . KB::get( 'google-updates', 'Google Core Updates in the Graphs' ) . '" target="_blank">' . __( 'Google Core Updates', 'rank-math-pro' ) . '</a>'
),
'default' => 'on',
],
++$fields_position
);
}
/**
* Check if certain fields got updated.
*
* @param int $object_id The ID of the current object.
* @param array $updated Array of field ids that were updated.
* Will only include field ids that had values change.
*/
public function sync_global_settings( $object_id, $updated ) {
if ( in_array( 'sync_global_setting', $updated, true ) ) {
\RankMathPro\Admin\Api::get()->sync_setting(
cmb2_get_option( $object_id, 'sync_global_setting' )
);
$this->send_summary();
}
}
/**
* Get local Analytics JS URL if the option is turned on.
*
* @return mixed
*/
public function get_local_gtag_js_url() {
$settings = $this->get_settings();
$validator_key = 'rank_math_local_ga_js_validator_' . md5( $settings['property_id'] );
$validator = get_transient( $validator_key );
if ( ! is_string( $validator ) || empty( $validator ) ) {
$validator = '1';
}
return add_query_arg( 'local_ga_js', $validator, trailingslashit( home_url() ) );
}
/**
* Serve Analytics JS from local cache if the option is turned on.
*
* @return void
*/
public function local_js_endpoint() {
if ( Param::get( 'local_ga_js' ) && $this->get_settings()['local_ga_js'] && $this->get_local_ga_js_contents() ) {
header( 'Content-Type: application/javascript' );
header( 'Cache-Control: max-age=604800, public' );
echo $this->get_local_ga_js_contents(); // phpcs:ignore
exit;
}
}
/**
* Get local cache of GA JS file contents or fetch new data.
*
* @param boolean $force_update Force update transient now.
* @return string
*/
public function get_local_ga_js_contents( $force_update = false ) {
$settings = $this->get_settings();
$cache_key = 'rank_math_local_ga_js_' . md5( $settings['property_id'] );
$validator_key = 'rank_math_local_ga_js_validator_' . md5( $settings['property_id'] );
$validator = md5( $cache_key . time() );
$stored = get_transient( $cache_key );
if ( false !== $stored && ! $force_update ) {
return $stored;
}
$response = wp_remote_get( 'https://www.googletagmanager.com/gtag/js?id=' . $settings['property_id'] );
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
set_transient( $cache_key, '', 12 * HOUR_IN_SECONDS );
return '';
}
$contents = wp_remote_retrieve_body( $response );
set_transient( $cache_key, $contents, 12 * HOUR_IN_SECONDS );
set_transient( $validator_key, $validator, 12 * HOUR_IN_SECONDS );
return $contents;
}
/**
* Filter gtag.js config array.
*
* @param array $config Config parameters.
* @return array
*/
public function gtag_config( $config ) {
$settings = $this->get_settings();
if ( ! empty( $settings['anonymize_ip'] ) ) {
$config[] = "'anonymize_ip': true";
}
return $config;
}
/**
* Filter function to add Google permissions used in Pro.
*
* @param array $data Array of System status data.
*/
public function google_permission_info( $data ) {
$data['fields']['permissions']['value'] = array_merge(
$data['fields']['permissions']['value'],
[
esc_html__( 'AdSense', 'rank-math-pro' ) => Permissions::get_status_text( Permissions::has_adsense() ),
esc_html__( 'Analytics', 'rank-math-pro' ) => Permissions::get_status_text( Permissions::has_analytics() ),
]
);
ksort( $data['fields']['permissions']['value'] );
return $data;
}
/**
* Filter inline JS & URL for gtag.js.
*
* @param array $gtag_data Array containing URL & inline code for the gtag script.
* @return array
*/
public function gtag( $gtag_data ) {
if ( is_admin() ) {
return $gtag_data;
}
$settings = $this->get_settings();
if ( empty( $settings['install_code'] ) ) {
return $gtag_data;
}
if ( ! empty( $settings['local_ga_js'] ) ) {
$gtag_data['url'] = $this->get_local_gtag_js_url();
}
return $gtag_data;
}
/**
* Filter winning and losing posts if needed.
*
* @param null $null Null.
* @param array $data Analytics data array.
* @param array $args Query arguments.
*
* @return mixed
*/
public function filter_winning_losing_posts( $null, $data, $args ) {
$order_by_field = $args['orderBy'];
$type = $args['type'];
$objects = $args['objects'];
if ( ! in_array( $type, [ 'win', 'lose' ], true ) ) {
return $null;
}
// Filter array by $type value.
$order_by_position = in_array( $order_by_field, [ 'diffPosition', 'position' ], true ) ? true : false;
if ( ( 'win' === $type && $order_by_position ) || ( 'lose' === $type && ! $order_by_position ) ) {
$data = array_filter(
$data,
function( $row ) use ( $order_by_field, $objects ) {
if ( $objects ) {
// Show Winning posts if difference is 80 or less.
return $row[ $order_by_field ] < 0 && $row[ $order_by_field ] > -80;
}
return $row[ $order_by_field ] < 0;
}
);
} elseif ( ( 'lose' === $type && $order_by_position ) || ( 'win' === $type && ! $order_by_position ) ) {
$data = array_filter(
$data,
function( $row ) use ( $order_by_field ) {
return $row[ $order_by_field ] > 0;
}
);
}
$data = $this->finalize_filtered_data( $data, $args );
return $data;
}
/**
* Filter winning keywords if needed.
*
* @param null $null Null.
* @param array $data Analytics data array.
* @param array $args Query arguments.
*
* @return mixed
*/
public function filter_winning_keywords( $null, $data, $args ) {
$order_by_field = $args['orderBy'];
$dimension = $args['dimension'];
if ( 'query' !== $dimension || 'diffPosition' !== $order_by_field || 'ASC' !== $args['order'] ) {
return $null;
}
// Filter array by $type value.
$data = array_filter(
$data,
function( $row ) use ( $order_by_field ) {
return $row[ $order_by_field ] < 0 && $row[ $order_by_field ] > -80;
}
);
$data = $this->finalize_filtered_data( $data, $args );
return $data;
}
/**
* Sort & limit keywords according to the args.
*
* @param array $data Data rows.
* @param array $args Query args.
*
* @return array
*/
private function finalize_filtered_data( $data, $args ) {
if ( ! empty( $args['order'] ) ) {
$sort_base_arr = array_column( $data, $args['orderBy'], $args['dimension'] );
array_multisort( $sort_base_arr, 'ASC' === $args['order'] ? SORT_ASC : SORT_DESC, $data );
}
$data = array_slice( $data, $args['offset'], $args['perpage'], true );
return $data;
}
/**
* Add search traffic value in seo detail of post lists
*
* @param int $post_id object Id.
*/
public function post_column_search_traffic( $post_id ) {
if ( ! Authentication::is_authorized() ) {
return;
}
$analytics = get_option( 'rank_math_google_analytic_options' );
$analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );
static $traffic_data;
if ( null === $traffic_data ) {
$post_ids = $this->get_queried_post_ids();
if ( empty( $post_ids ) ) {
$traffic_data = [];
return;
}
$traffic_data = $analytics_connected ? Pageviews::get_traffic_by_object_ids( $post_ids ) : Pageviews::get_impressions_by_object_ids( $post_ids );
}
if ( ! isset( $traffic_data[ $post_id ] ) ) {
return;
}
?>
<span class="rank-math-column-display rank-math-search-traffic">
<strong>
<?php
$analytics_connected
? esc_html_e( 'Search Traffic:', 'rank-math-pro' )
: esc_html_e( 'Search Impression:', 'rank-math-pro' );
?>
</strong>
<?php echo esc_html( number_format( $traffic_data[ $post_id ] ) ); ?>
</span>
<?php
}
/**
* Extend the date_exists() function to include the additional tables.
*
* @param string $tables Tables.
* @return string
*/
public function date_exists_tables( $tables ) {
$tables['analytics'] = DB_Helper::check_table_exists( 'rank_math_analytics_ga' ) ? 'rank_math_analytics_ga' : '';
$tables['adsense'] = DB_Helper::check_table_exists( 'rank_math_analytics_adsense' ) ? 'rank_math_analytics_adsense' : '';
return $tables;
}
/**
* Get queried post ids.
*/
private function get_queried_post_ids() {
global $wp_query;
if ( empty( $wp_query->posts ) ) {
return false;
}
$post_ids = array_filter(
array_map(
function( $post ) {
return isset( $post->ID ) ? $post->ID : '';
},
$wp_query->posts
)
);
return $post_ids;
}
}

View File

@@ -0,0 +1,651 @@
<?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;
}
}

View File

@@ -0,0 +1,542 @@
<?php
/**
* Pro SEO Reports in Email.
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\KB;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use RankMath\Admin\Admin_Helper;
use RankMath\Google\Authentication;
use RankMath\Analytics\Email_Reports as Email_Reports_Base;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
use RankMathPro\Analytics\Keywords;
use RankMathPro\Analytics\Posts;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* DB class.
*/
class Email_Reports {
use Hooker;
/**
* Path to the views folder.
*
* @var string
*/
public $views_path = '';
/**
* URL of the module's assets folder.
*
* @var string
*/
public $assets_url = '';
/**
* The constructor.
*/
public function __construct() {
$this->hooks();
}
/**
* Add filter & action hooks.
*
* @return void
*/
public function hooks() {
$this->views_path = dirname( __FILE__ ) . '/views/email-reports/';
$this->assets_url = plugin_dir_url( __FILE__ ) . 'assets/';
// CMB hooks.
$this->action( 'rank_math/admin/settings/analytics', 'add_options' );
// WP hooks.
$this->filter( 'admin_post_rank_math_save_wizard', 'save_wizard' );
// Rank Math hooks.
$this->filter( 'rank_math/analytics/email_report_template_paths', 'add_template_path' );
$this->filter( 'rank_math/analytics/email_report_variables', 'add_variables' );
$this->filter( 'rank_math/analytics/email_report_parameters', 'email_parameters' );
$this->filter( 'rank_math/analytics/email_report_image_atts', 'replace_logo', 10, 2 );
$this->filter( 'rank_math/analytics/email_report_periods', 'frequency_periods' );
$this->action( 'rank_math/analytics/options/wizard_after_email_report', 'wizard_options' );
}
/**
* Output CSS for required for the Pro reports.
*
* @return void
*/
public function add_pro_css() {
$this->template_part( 'pro-style' );
}
/**
* Replace logo image in template.
*
* @param array $atts All original attributes.
* @param string $url Image URL or identifier.
*
* @return array
*/
public function replace_logo( $atts, $url ) {
if ( 'report-logo.png' !== $url ) {
return $atts;
}
$atts['src'] = '###LOGO_URL###';
$atts['alt'] = '###LOGO_ALT###';
return $atts;
}
/**
* Add Pro variables.
*
* @param array $variables Original variables.
* @return array
*/
public function add_variables( $variables ) {
$variables['pro_assets_url'] = $this->assets_url;
$variables['logo_url'] = Email_Reports_Base::get_setting( 'logo', $this->get_logo_url_default() );
$variables['logo_alt'] = __( 'Logo', 'rank-math-pro' );
$image_id = Email_Reports_Base::get_setting( 'logo_id', 0 );
if ( $image_id ) {
$alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
if ( $alt ) {
$variables['logo_alt'] = $alt;
}
}
$variables['header_background'] = Email_Reports_Base::get_setting( 'header_background', 'linear-gradient(90deg, rgba(112,83,181,1) 0%, rgba(73,153,210,1) 100%)' );
$variables['top_html'] = wp_kses_post( wpautop( Email_Reports_Base::get_setting( 'top_text', '' ) ) );
$variables['footer_html'] = wp_kses_post( Email_Reports_Base::get_setting( 'footer_text', $this->get_default_footer_text() ) );
$variables['custom_css'] = Email_Reports_Base::get_setting( 'custom_css', '' );
$variables['logo_link'] = Email_Reports_Base::get_setting( 'logo_link', KB::get( 'email-reports', 'PRO Email Report Logo' ) );
// Get Pro stats.
$period = Email_Reports_Base::get_period_from_frequency();
Stats::get()->set_date_range( "-{$period} days" );
$keywords = Keywords::get();
if ( Email_Reports_Base::get_setting( 'tracked_keywords', false ) ) {
$variables['winning_keywords'] = $keywords->get_tracked_winning_keywords();
$variables['losing_keywords'] = $keywords->get_tracked_losing_keywords();
} else {
$variables['winning_keywords'] = $keywords->get_winning_keywords();
$variables['losing_keywords'] = $keywords->get_losing_keywords();
}
$posts = Posts::get();
$variables['winning_posts'] = $posts->get_winning_posts();
$variables['losing_posts'] = $posts->get_losing_posts();
return $variables;
}
/**
* Add Email Report options.
*
* @param object $cmb CMB object.
*/
public function add_options( $cmb ) {
if ( ! Authentication::is_authorized() || Email_Reports_Base::are_fields_hidden() ) {
return;
}
$is_business = ProAdminHelper::is_business_plan();
// Add Frequency options.
$frequency_field = $cmb->get_field( 'console_email_frequency' );
// Early bail if the console_email_frequency field does not exist.
if ( empty( $frequency_field ) ) {
return;
}
$frequency_field->args['options']['every_15_days'] = esc_html__( 'Every 15 Days', 'rank-math-pro' );
$field_ids = wp_list_pluck( $cmb->prop( 'fields' ), 'id' );
$fields_position = array_search( 'console_email_frequency', array_keys( $field_ids ), true ) + 1;
if ( $is_business ) {
$frequency_field->args['options']['weekly'] = esc_html__( 'Every 7 Days', 'rank-math-pro' );
} else {
/**
* This field is repeated further down, to insert it in the correct
* position when the account type is Business.
*/
$cmb->add_field(
[
'id' => 'console_email_tracked_keywords',
'type' => 'toggle',
'name' => __( 'Include Only Tracked Keywords', 'rank-math-pro' ),
'description' => __( 'When enabled, the Winning Keywords section will only show Tracked Keywords.', 'rank-math-pro' ),
'default' => 'off',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
return;
}
// Business options from here on.
$cmb->add_field(
[
'id' => 'console_email_send_to',
'type' => 'text',
'name' => __( 'Report Email Address', 'rank-math-pro' ),
'description' => __( 'Address where the reports will be sent. You can add multiple recipients separated with commas.', 'rank-math-pro' ),
'default' => Admin_Helper::get_registration_data()['email'],
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_subject',
'type' => 'text',
'name' => __( 'Report Email Subject', 'rank-math-pro' ),
'description' => __( 'Subject of the report emails.', 'rank-math-pro' ),
'default' => $this->get_subject_default(),
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_logo',
'type' => 'file',
'name' => __( 'Report Logo', 'rank-math-pro' ),
'description' => __( 'Logo appearing in the header part of the report.', 'rank-math-pro' ),
'default' => $this->get_logo_url_default(),
'options' => [ 'url' => false ],
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_logo_link',
'type' => 'text',
'name' => __( 'Logo Link', 'rank-math-pro' ),
'description' => __( 'URL where the logo link should point to.', 'rank-math-pro' ),
'default' => KB::get( 'email-reports-logo', 'PRO Email Report Logo' ),
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_header_background',
'type' => 'text',
'name' => __( 'Report Header Background', 'rank-math-pro' ),
'description' => __( 'Color hex code or any other valid value for the <code>background:</code> CSS property.', 'rank-math-pro' ),
'default' => $this->get_header_bg_default(),
'dep' => [ [ 'console_email_reports', 'on' ] ],
// Instant preview.
'after_field' => $this->get_bg_preview(),
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_link_full_report',
'type' => 'toggle',
'name' => __( 'Link to Full Report', 'rank-math-pro' ),
'description' => __( 'Select whether to include a link to the Full Report admin page in the email or not.', 'rank-math-pro' ),
'default' => 'on',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_top_text',
'type' => 'textarea_small',
'name' => __( 'Report Top Text', 'rank-math-pro' ),
'description' => __( 'Text or basic HTML to insert below the title.', 'rank-math-pro' ),
'default' => '',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_sections',
'type' => 'multicheck',
'name' => esc_html__( 'Include Sections', 'rank-math-pro' ),
'desc' => esc_html__( 'Select which tables to show in the report.', 'rank-math-pro' ),
'options' => [
'summary' => __( 'Basic Summary', 'rank-math-pro' ),
'positions' => __( 'Positions Summary', 'rank-math-pro' ),
'winning_posts' => __( 'Top Winning Posts', 'rank-math-pro' ),
'losing_posts' => __( 'Top Losing Posts', 'rank-math-pro' ),
'winning_keywords' => __( 'Top Winning Keywords', 'rank-math-pro' ),
'losing_keywords' => __( 'Top Losing Keywords', 'rank-math-pro' ),
],
'default' => [ 'summary', 'positions', 'winning_posts', 'winning_keywords', 'losing_keywords' ],
'select_all_button' => true,
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
/**
* This field is also added for non-business accounts at the beginning
* of this function.
*/
$cmb->add_field(
[
'id' => 'console_email_tracked_keywords',
'type' => 'toggle',
'name' => __( 'Include Only Tracked Keywords', 'rank-math-pro' ),
'description' => __( 'When enabled, the Winning Keywords and Losing Keywords sections will only show Tracked Keywords.', 'rank-math-pro' ),
'default' => 'off',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_footer_text',
'type' => 'textarea_small',
'name' => __( 'Report Footer Text', 'rank-math-pro' ),
'description' => __( 'Text or basic HTML to insert in the footer area.', 'rank-math-pro' ),
'default' => $this->get_default_footer_text(),
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
$cmb->add_field(
[
'id' => 'console_email_custom_css',
'type' => 'textarea_small',
'name' => __( 'Additional CSS code', 'rank-math-pro' ),
'description' => __( 'Additional CSS code to customize the appearance of the reports. Insert the CSS code directly, without the wrapping style tag. Please note that the CSS support is limited in email clients and the appearance may vary greatly.', 'rank-math-pro' ),
'default' => '',
'dep' => [ [ 'console_email_reports', 'on' ] ],
],
++$fields_position
);
}
/**
* Get default value for footer text option.
*
* @return string
*/
public function get_default_footer_text() {
return join(
' ',
[
// Translators: placeholder is a link to the homepage.
sprintf( esc_html__( 'This email was sent to you as a registered member of %s.', 'rank-math-pro' ), '<a href="###SITE_URL###">###SITE_URL_SIMPLE###</a>' ),
// Translators: placeholder is a link to the settings, with "click here" as the anchor text.
sprintf( esc_html__( 'To update your email preferences, %s. ###ADDRESS###', 'rank-math-pro' ), '<a href="###SETTINGS_URL###">' . esc_html__( 'click here', 'rank-math-pro' ) . '</a>' ),
]
);
}
/**
* Change email parameters if needed.
*
* @param array $email Parameters array.
* @return array
*/
public function email_parameters( $email ) {
$email['to'] = Email_Reports_Base::get_setting( 'send_to', Admin_Helper::get_registration_data()['email'] );
$email['subject'] = Email_Reports_Base::get_setting( 'subject', $this->get_subject_default() );
return $email;
}
/**
* Get 'value' & 'diff' for the stat template part.
*
* @param mixed $data Stats data.
* @param string $item Item we want to extract.
* @return array
*/
public static function get_stats_val( $data, $item ) {
$value = isset( $data[ $item ]['total'] ) ? $data[ $item ]['total'] : 0;
$diff = isset( $data[ $item ]['difference'] ) ? $data[ $item ]['difference'] : 0;
return compact( 'value', 'diff' );
}
/**
* Output additional options in the Setup Wizard.
*
* @return void
*/
public function wizard_options() {
if ( ! ProAdminHelper::is_business_plan() ) {
return;
}
?>
<div class="cmb-row cmb-type-toggle cmb2-id-console-email-send-to" data-fieldtype="toggle">
<div class="cmb-th">
<label for="console_email_send"><?php esc_html_e( 'Report Email Address', 'rank-math-pro' ); ?></label>
</div>
<div class="cmb-td">
<input type="text" class="regular-text" name="console_email_send_to" id="console_email_send_to" value="<?php echo esc_attr( Helper::get_settings( 'general.console_email_send_to' ) ); ?>" data-hash="42cpi4bihms0">
<p class="cmb2-metabox-description"><?php esc_html_e( 'Address where the reports will be sent. You can add multiple recipients separated with commas.', 'rank-math-pro' ); ?></p>
<div class="rank-math-cmb-dependency hidden" data-relation="or"><span class="hidden" data-field="console_email_reports" data-comparison="=" data-value="on"></span></div>
</div>
</div>
<?php
}
/**
* Save additional wizard options.
*
* @return bool
*/
public function save_wizard() {
$referer = Param::post( '_wp_http_referer' );
if ( empty( $_POST ) ) {
return wp_safe_redirect( $referer );
}
check_admin_referer( 'rank-math-wizard', 'security' );
if ( ! Helper::has_cap( 'general' ) ) {
return false;
}
$send_to = Param::post( 'console_email_send_to' );
if ( ! $send_to ) {
return true;
}
$settings = rank_math()->settings->all_raw();
$settings['general']['console_email_send_to'] = $send_to;
Helper::update_all_settings( $settings['general'], null, null );
return true;
}
/**
* Add element and script for background preview.
*
* @return string
*/
public function get_bg_preview() {
$script = '
<script>
jQuery( function() {
jQuery( "#console_email_header_background" ).on( "change", function() {
jQuery( ".rank-math-preview-bg" ).css( "background", jQuery( this ).val() );
} );
} );
</script>
';
return '<div class="rank-math-preview-bg" data-title="' . esc_attr( __( 'Preview', 'rank-math-pro' ) ) . '" style="background: ' . esc_attr( Helper::get_settings( 'general.console_email_header_background', $this->get_header_bg_default() ) ) . '"></div>' . $script;
}
/**
* Get default value for the Header Background option.
*
* @return string
*/
public function get_header_bg_default() {
return 'linear-gradient(90deg, #724BB7 0%, #4098D7 100%)';
}
/**
* Get default value for the Logo URL option.
*
* @return string
*/
public function get_logo_url_default() {
$url = \rank_math()->plugin_url() . 'includes/modules/analytics/assets/img/';
return $url . 'report-logo.png';
}
/**
* Get default value for the Subject option.
*
* @return string
*/
public function get_subject_default() {
return sprintf(
// Translators: placeholder is the site URL.
__( 'Rank Math [SEO Report] - %s', 'rank-math-pro' ),
explode( '://', get_home_url() )[1]
);
}
/**
* Shorten a URL, like http://example-url...long-page/
*
* @param string $url URL to shorten.
* @param integer $max Max length in characters.
* @return string
*/
public static function shorten_url( $url, $max = 16 ) {
$length = strlen( $url );
if ( $length <= $max + 3 ) {
return $url;
}
return substr_replace( $url, '...', $max / 2, $length - $max );
}
/**
* Add pro template path to paths.
*
* @param string[] $paths Original paths.
* @return string[]
*/
public function add_template_path( $paths ) {
$paths[] = $this->views_path;
return $paths;
}
/**
* Add day numbers for new frequencies.
*
* @param array $periods Original periods.
* @return array
*/
public function frequency_periods( $periods ) {
$periods['every_15_days'] = 15;
$periods['weekly'] = 7;
return $periods;
}
}

View File

@@ -0,0 +1,752 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use WP_REST_Request;
use RankMath\Traits\Cache;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use RankMath\Helper;
use MyThemeShop\Helpers\Param;
defined( 'ABSPATH' ) || exit;
/**
* Keywords class.
*/
class Keywords {
use Hooker, Cache;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Keywords
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Keywords ) ) {
$instance = new Keywords();
$instance->setup();
}
return $instance;
}
/**
* Initialize filter.
*/
public function setup() {
$this->filter( 'rank_math/analytics/keywords', 'add_keyword_position_graph' );
$this->filter( 'rank_math/analytics/keywords_overview', 'add_winning_losing_data' );
$this->action( 'save_post', 'add_post_focus_keyword' );
$this->action( 'init', 'get_post_type_list', 99 );
}
/**
* Get accessible post type lists to auto add the focus keywords.
*/
public function get_post_type_list() {
if ( 'rank-math-analytics' !== Param::get( 'page' ) ) {
return;
}
$post_types = array_map(
function( $post_type ) {
return 'attachment' === $post_type ? false : Helper::get_post_type_label( $post_type );
},
Helper::get_accessible_post_types()
);
Helper::add_json( 'postTypes', array_filter( $post_types ) );
Helper::add_json( 'autoAddFK', Helper::get_settings( 'general.auto_add_focus_keywords', [] ) );
}
/**
* Get keywords position data to show it in the graph.
*
* @param array $rows Rows.
* @return array
*/
public function add_keyword_position_graph( $rows ) {
$history = $this->get_graph_data_for_keywords( \array_keys( $rows ) );
$rows = Stats::get()->set_query_position( $rows, $history );
return $rows;
}
/**
* Get winning and losing keywords data.
*
* @param array $data Data.
* @return array
*/
public function add_winning_losing_data( $data ) {
$data['winningKeywords'] = $this->get_winning_keywords();
$data['losingKeywords'] = $this->get_losing_keywords();
if ( empty( $data['winningKeywords'] ) ) {
$data['winningKeywords']['response'] = 'No Data';
}
if ( empty( $data['losingKeywords'] ) ) {
$data['losingKeywords']['response'] = 'No Data';
}
return $data;
}
/**
* Extract keywords that can be added by removing the empty and the duplicate keywords.
*
* @param string $keywords Comma Separated Keyword List.
*
* @return array Keywords that can be added.
*/
public function extract_addable_track_keyword( $keywords ) {
global $wpdb;
// Split keywords.
$keywords_to_add = array_filter( array_map( 'trim', explode( ',', $keywords ) ) );
$keywords_to_check = array_filter( array_map( 'mb_strtolower', explode( ',', $keywords ) ) );
// Check if keywords already exists.
$keywords_joined = "'" . join( "', '", array_map( 'esc_sql', $keywords_to_add ) ) . "'";
$query = "SELECT keyword FROM {$wpdb->prefix}rank_math_analytics_keyword_manager as km WHERE km.keyword IN ( $keywords_joined )";
$data = $wpdb->get_results( $query ); // phpcs:ignore
// Filter out non-existing keywords.
foreach ( $data as $row ) {
$key = array_search( mb_strtolower( $row->keyword ), $keywords_to_check, true );
if ( false !== $key ) {
unset( $keywords_to_add[ $key ] );
}
}
return $keywords_to_add;
}
/**
* Add keyword to Rank Tracker.
*
* @param array $keywords Keyword List.
*/
public function add_track_keyword( $keywords ) {
foreach ( $keywords as $add_keyword ) {
DB::keywords()->insert(
[
'keyword' => $add_keyword,
'collection' => 'uncategorized',
'is_active' => true,
],
[ '%s', '%s', '%d' ]
);
}
delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
}
/**
* Remove a keyword from Rank Tracker.
*
* @param string $keyword Keyword to remove.
*/
public function remove_track_keyword( $keyword ) {
DB::keywords()->where( 'keyword', $keyword )
->delete();
delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
}
/**
* Delete all tracked keywords.
*/
public function delete_all_tracked_keywords() {
DB::keywords()->delete();
delete_transient( Stats::get()->get_cache_key( 'tracked_keywords_summary', Stats::get()->days . 'days' ) );
}
/**
* Get tracked keywords count.
*
* @return int Total keywords count
*/
public function get_tracked_keywords_count() {
$total = DB::keywords()
->selectCount( 'DISTINCT(keyword)', 'total' )
->where( 'is_active', 1 )
->getVar();
return (int) $total;
}
/**
* Get keywords quota.
*
* @return array Keywords usage info.
*/
public function get_tracked_keywords_quota() {
$quota = (array) get_option(
'rank_math_keyword_quota',
[
'taken' => 0,
'available' => 0,
]
);
return $quota;
}
/**
* Get tracked keywords summary.
*
* @return array Keywords usage info.
*/
public function get_tracked_keywords_summary() {
$cache_key = 'tracked_keywords_summary';
$cache_group = 'tracked_keywords_summary';
$summary = $this->get_cache( $cache_key, $cache_group );
if ( empty( $summary ) ) {
$summary = $this->get_tracked_keywords_quota();
$summary['total'] = $this->get_tracked_keywords_count();
$this->set_cache( $cache_key, $summary, $cache_group, DAY_IN_SECONDS );
}
return $summary;
}
/**
* Get winning tracked keywords.
*
* @return array Top 5 winning tracked keywords data.
*/
public function get_tracked_winning_keywords() {
return $this->get_tracked_keywords(
[
'offset' => 0,
'perpage' => 5,
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) < 0',
]
);
}
/**
* Get losing tracked keywords.
*
* @return array Top 5 losing tracked keywords data.
*/
public function get_tracked_losing_keywords() {
return $this->get_tracked_keywords(
[
'order' => 'DESC',
'offset' => 0,
'perpage' => 5,
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) > 0',
]
);
}
/**
* Get tracked keywords rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array Tracked keywords data.
*/
public function get_tracked_keywords_rows( WP_REST_Request $request ) {
$per_page = 25;
$cache_args = $request->get_params();
$cache_args['per_page'] = $per_page;
$cache_group = 'rank_math_rest_tracked_keywords_rows';
$cache_key = $this->generate_hash( $cache_args );
$result = $this->get_cache( $cache_key, $cache_group );
if ( ! empty( $result ) ) {
return $result;
}
$page = ! empty( $request->get_param( 'page' ) ) ? $request->get_param( 'page' ) : 1;
$orderby = ! empty( $request->get_param( 'orderby' ) ) ? $request->get_param( 'orderby' ) : 'default';
$order = ! empty( $request->get_param( 'order' ) ) ? strtoupper( $request->get_param( 'order' ) ) : 'DESC';
$keyword = ! empty( $request->get_param( 'search' ) ) ? filter_var( urldecode( $request->get_param( 'search' ) ), FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_BACKTICK ) : '';
$offset = ( $page - 1 ) * $per_page;
$args = wp_parse_args(
[
'dimension' => 'query',
'limit' => "LIMIT {$offset}, {$per_page}",
'keyword' => $keyword,
]
);
switch ( $orderby ) {
case 'impressions':
case 'clicks':
case 'ctr':
case 'position':
$args['orderBy'] = $orderby;
$args['order'] = $order;
break;
case 'query':
$args['orderBy'] = 'keyword';
$args['order'] = $order;
break;
}
$data = $this->get_tracked_keywords_data( $args );
$data = Stats::get()->set_dimension_as_key( $data );
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
if ( 'default' === $orderby ) {
uasort(
$data,
function( $a, $b ) use ( $orderby ) {
if ( false === array_key_exists( 'position', $a ) ) {
$a['position'] = [ 'total' => '0' ];
}
if ( false === array_key_exists( 'position', $b ) ) {
$b['position'] = [ 'total' => '0' ];
}
if ( 0 === intval( $b['position']['total'] ) ) {
return 0;
}
return $a['position']['total'] > $b['position']['total'];
}
);
}
$result['rowsData'] = $data;
// get total rows by search.
$args = wp_parse_args(
[
'dimension' => 'query',
'limit' => 'LIMIT 10000',
'keyword' => $keyword,
]
);
if ( empty( $data ) ) {
$result['response'] = 'No Data';
} else {
$search_data = $this->get_tracked_keywords_data( $args );
$result['total'] = count( $search_data );
$this->set_cache( $cache_key, $result, $cache_group, DAY_IN_SECONDS );
}
return $result;
}
/**
* Get keyword rows from keyword manager table.
*
* @param array $args Array of arguments.
* @return array
*/
public function get_tracked_keywords_data( $args = [] ) {
global $wpdb;
Helper::enable_big_selects_for_queries();
$args = wp_parse_args(
$args,
[
'dimension' => 'query',
'order' => 'ASC',
'orderBy' => 'diffPosition1',
'objects' => false,
'where' => '',
'sub_where' => '',
'dates' => ' AND created BETWEEN %s AND %s',
'limit' => 'LIMIT 5',
'keyword' => '',
]
);
$where = $args['where'];
$limit = $args['limit'];
$dimension = $args['dimension'];
$sub_where = $args['sub_where'];
$dates = $args['dates'];
$keyword = trim( $args['keyword'] );
$order = sprintf( 'ORDER BY %s %s', $args['orderBy'], $args['order'] );
$dates_query = sprintf( " AND created BETWEEN '%s' AND '%s' ", Stats::get()->start_date, Stats::get()->end_date );
// Step1. Get most recent data row id for each keyword.
// phpcs:disable
$where_like_keyword = $wpdb->prepare( ' WHERE keyword LIKE %s', '%' . $wpdb->esc_like( $keyword ) . '%' );
if ( empty( $keyword ) ) {
$where_like_keyword = '';
}
$query = "SELECT MAX(id) as id FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1 {$dates_query} AND {$dimension} IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager {$where_like_keyword} GROUP BY keyword ) GROUP BY {$dimension}";
$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 data row id for each keyword (for comparison).
// phpcs:disable
$dates_query = sprintf( " AND created BETWEEN '%s' AND '%s' ", Stats::get()->compare_start_date, Stats::get()->compare_end_date );
$query = "SELECT MAX(id) as id FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1 {$dates_query} AND {$dimension} IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager {$where_like_keyword} GROUP BY keyword ) GROUP BY {$dimension}";
$old_ids = $wpdb->get_results( $query );
// 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 most performing keywords first based on id list from above.
$where_like_keyword1 = $wpdb->prepare( ' WHERE km.keyword LIKE %s', '%' . $wpdb->esc_like( $keyword ) . '%' );
if ( empty( $keyword ) ) {
$where_like_keyword1 = '';
}
$positions = $wpdb->get_results(
"SELECT DISTINCT(km.keyword) as {$dimension}, COALESCE(t.position, 0) as position, COALESCE(t.diffPosition, 0) as diffPosition, COALESCE(t.diffPosition, 100) as diffPosition1, COALESCE(t.impressions, 0) as impressions, COALESCE(t.diffImpressions, 0) as diffImpressions, COALESCE(t.clicks, 0) as clicks, COALESCE(t.diffClicks, 0) as diffClicks, COALESCE(t.ctr, 0) as ctr, COALESCE(t.diffCtr, 0) as diffCtr
FROM {$wpdb->prefix}rank_math_analytics_keyword_manager km
LEFT JOIN (
SELECT
t1.{$dimension} as {$dimension}, ROUND( t1.position, 0 ) as position, ROUND( t1.impressions, 0 ) as impressions, ROUND( t1.clicks, 0 ) as clicks, ROUND( t1.ctr, 0 ) as ctr,
COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) as diffPosition,
COALESCE( ROUND( t1.impressions - COALESCE( t2.impressions, 100 ), 0 ), 0 ) as diffImpressions,
COALESCE( ROUND( t1.clicks - COALESCE( t2.clicks, 100 ), 0 ), 0 ) as diffClicks,
COALESCE( ROUND( t1.ctr - COALESCE( t2.ctr, 100 ), 0 ), 0 ) as diffCtr
FROM
(SELECT a.{$dimension}, a.position, a.impressions,a.clicks,a.ctr FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
WHERE 1 = 1{$ids_where}) AS t1
LEFT JOIN
(SELECT a.{$dimension}, a.position, a.impressions,a.clicks,a.ctr FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
WHERE 1 = 1{$old_ids_where}) AS t2
ON t1.{$dimension} = t2.{$dimension}) AS t on t.{$dimension} = km.keyword
{$where_like_keyword1}
{$where}
{$order}
{$limit}",
ARRAY_A
);
// phpcs:enable
// Step6. Get keywords list from above results.
$keywords = array_column( $positions, 'query' );
$keywords = array_map( 'esc_sql', $keywords );
$keywords = array_map( 'strtolower', $keywords );
$keywords = '(\'' . join( '\', \'', $keywords ) . '\')';
// step7. Get other metrics data.
$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(position) as position, AVG(ctr) as ctr FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1{$dates} AND {$dimension} IN {$keywords} GROUP BY {$dimension}) as t1
LEFT JOIN
( SELECT {$dimension}, SUM( clicks ) as clicks, SUM(impressions) as impressions, AVG(position) as position, AVG(ctr) as ctr FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE 1 = 1{$dates} AND {$dimension} IN {$keywords} GROUP BY {$dimension}) as t2
ON t1.query = t2.query",
Stats::get()->start_date,
Stats::get()->end_date,
Stats::get()->compare_start_date,
Stats::get()->compare_end_date
);
$metrics = $wpdb->get_results( $query, ARRAY_A );
// Step8. Merge above two results.
$positions = Stats::get()->set_dimension_as_key( $positions, $dimension );
$metrics = Stats::get()->set_dimension_as_key( $metrics, $dimension );
$data = Stats::get()->get_merged_metrics( $positions, $metrics );
// Step9. Construct return data.
foreach ( $data as $keyword => $row ) {
$data[ $keyword ]['graph'] = [];
$data[ $keyword ]['clicks'] = [
'total' => (int) $data[ $keyword ]['clicks'],
'difference' => (int) $data[ $keyword ]['diffClicks'],
];
$data[ $keyword ]['impressions'] = [
'total' => (int) $data[ $keyword ]['impressions'],
'difference' => (int) $data[ $keyword ]['diffImpressions'],
];
$data[ $keyword ]['position'] = [
'total' => (float) $data[ $keyword ]['position'],
'difference' => (float) $data[ $keyword ]['diffPosition'],
];
$data[ $keyword ]['ctr'] = [
'total' => (float) $data[ $keyword ]['ctr'],
'difference' => (float) $data[ $keyword ]['diffCtr'],
];
unset(
$data[ $keyword ]['diffClicks'],
$data[ $keyword ]['diffImpressions'],
$data[ $keyword ]['diffPosition'],
$data[ $keyword ]['diffCtr']
);
}
return $data;
}
/**
* Get tracked keywords.
*
* @param array $args Array of arguments.
* @return array
*/
public function get_tracked_keywords( $args = [] ) {
global $wpdb;
$args = wp_parse_args(
$args,
[
'dimension' => 'query',
'order' => 'ASC',
'orderBy' => 'diffPosition',
'offset' => 0,
'perpage' => 20000,
'sub_where' => " AND query IN ( SELECT keyword from {$wpdb->prefix}rank_math_analytics_keyword_manager )",
]
);
$data = Stats::get()->get_analytics_data( $args );
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
// Add remaining keywords.
if ( 5 !== $args['perpage'] ) {
$rows = DB::keywords()->get();
foreach ( $rows as $row ) {
if ( ! isset( $data[ $row->keyword ] ) ) {
$data[ $row->keyword ] = [
'query' => $row->keyword,
'graph' => [],
'clicks' => [
'total' => 0,
'difference' => 0,
],
'impressions' => [
'total' => 0,
'difference' => 0,
],
'position' => [
'total' => 0,
'difference' => 0,
],
'ctr' => [
'total' => 0,
'difference' => 0,
],
'pageviews' => [
'total' => 0,
'difference' => 0,
],
];
}
}
}
return $data;
}
/**
* Get most recent day's keywords.
*
* @return array
*/
public function get_recent_keywords() {
global $wpdb;
$query = $wpdb->prepare(
"SELECT query
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE DATE(created) = (SELECT MAX(DATE(created)) FROM {$wpdb->prefix}rank_math_analytics_gsc WHERE created BETWEEN %s AND %s)
GROUP BY query",
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query ); // phpcs:ignore
return $data;
}
/**
* Get top 5 winning keywords.
*
* @return array
*/
public function get_winning_keywords() {
$cache_key = Stats::get()->get_cache_key( 'winning_keywords', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
// Get most recent day's keywords only.
$keywords = $this->get_recent_keywords();
$keywords = wp_list_pluck( $keywords, 'query' );
$keywords = array_map( 'strtolower', $keywords );
$data = Stats::get()->get_analytics_data(
[
'order' => 'ASC',
'dimension' => 'query',
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) < 0',
]
);
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
set_transient( $cache_key, $data, DAY_IN_SECONDS );
return $data;
}
/**
* Get top 5 losing keywords.
*
* @return array
*/
public function get_losing_keywords() {
$cache_key = Stats::get()->get_cache_key( 'losing_keywords', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
// Get most recent day's keywords only.
$keywords = $this->get_recent_keywords();
$keywords = wp_list_pluck( $keywords, 'query' );
$keywords = array_map( 'strtolower', $keywords );
$data = Stats::get()->get_analytics_data(
[
'dimension' => 'query',
'where' => 'WHERE COALESCE( ROUND( t1.position - COALESCE( t2.position, 100 ), 0 ), 0 ) > 0',
]
);
$history = $this->get_graph_data_for_keywords( \array_keys( $data ) );
$data = Stats::get()->set_query_position( $data, $history );
set_transient( $cache_key, $data, DAY_IN_SECONDS );
return $data;
}
/**
* Get keywords graph data.
*
* @param array $keywords Keywords to get data for.
*
* @return array
*/
public function get_graph_data_for_keywords( $keywords ) {
global $wpdb;
$intervals = Stats::get()->get_intervals();
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$keywords = \array_map( 'esc_sql', $keywords );
$keywords = '(\'' . join( '\', \'', $keywords ) . '\')';
$query = $wpdb->prepare(
"SELECT a.query, a.position, t.date, t.range_group
FROM {$wpdb->prefix}rank_math_analytics_gsc AS a
INNER JOIN
(SELECT query, DATE_FORMAT(created, '%%Y-%%m-%%d') as date, MAX(id) as id, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s
AND query IN {$keywords}
GROUP BY query, range_group
ORDER BY created ASC) AS t ON a.id = t.id
",
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query );
// phpcs:enable
$data = Stats::get()->filter_graph_rows( $data );
return array_map( [ Stats::get(), 'normalize_graph_rows' ], $data );
}
/**
* Get pages by keyword.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_keyword_pages( WP_REST_Request $request ) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT DISTINCT g.page
FROM {$wpdb->prefix}rank_math_analytics_gsc as g
WHERE g.query = %s AND g.created BETWEEN %s AND %s
ORDER BY g.created DESC
LIMIT 5",
$request->get_param( 'query' ),
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query ); // phpcs:ignore
$pages = wp_list_pluck( $data, 'page' );
$console = Stats::get()->get_analytics_data(
[
'objects' => true,
'pageview' => true,
'sub_where' => " AND page IN ('" . join( "', '", $pages ) . "')",
]
);
return $console;
}
/**
* Add focus keywords to Rank Tracker.
*
* @param int $post_id Post ID.
* @return mixed
*/
public function add_post_focus_keyword( $post_id ) {
if ( wp_is_post_revision( $post_id ) ) {
return;
}
$auto_add_fks = Helper::get_settings( 'general.auto_add_focus_keywords', [] );
if (
empty( $auto_add_fks['enable_auto_import'] ) ||
empty( $auto_add_fks['post_types'] ) ||
! in_array( get_post_type( $post_id ), $auto_add_fks['post_types'], true )
) {
return;
}
$focus_keyword = Helper::get_post_meta( 'focus_keyword', $post_id );
if ( empty( $focus_keyword ) ) {
return;
}
$keywords_data = [];
$keywords = explode( ',', $focus_keyword );
if ( ! empty( $auto_add_fks['secondary_keyword'] ) ) {
$keywords_data = $keywords;
} else {
$keywords_data[] = current( $keywords );
}
DB::bulk_insert_query_focus_keyword_data( $keywords_data );
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
/**
* Links class.
*/
class Links {
/**
* Get links by post.
*
* @param array $objects Array of ids.
* @return array
*/
public static function get_links_by_objects( $objects ) {
if ( empty( $objects ) || empty( $objects['rows'] ) ) {
return [];
}
if ( ! Helper::is_module_active( 'link-counter' ) ) {
return $objects;
}
$ids = wp_list_pluck( $objects['rows'], 'object_id' );
$links = DB::links()
->whereIn( 'object_id', \array_unique( $ids ) )
->get( ARRAY_A );
$ids = array_flip( $ids );
foreach ( $links as $link ) {
$post_id = $link['object_id'];
$object_id = $ids[ $post_id ];
$objects['rows'][ $object_id ]['links'] = [
'internal' => $link['internal_link_count'],
'external' => $link['external_link_count'],
'incoming' => $link['incoming_link_count'],
];
}
return $objects;
}
}

View File

@@ -0,0 +1,199 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Analytics\Stats;
use MyThemeShop\Helpers\DB as DB_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Pageviews class.
*/
class Pageviews {
/**
* Get page views for pages.
*
* @param array $args Array of urls.
*
* @return array
*/
public static function get_pageviews( $args = [] ) {
global $wpdb;
$args = wp_parse_args(
$args,
[
'order' => 'DESC',
'orderBy' => 't1.pageviews',
'where' => '',
'sub_where' => '',
'dates' => ' AND created BETWEEN %s AND %s',
'limit' => '',
'pages' => '',
]
);
if ( empty( $args['pages'] ) ) {
return [
'rows' => [],
'rowsFound' => 0,
];
}
$args['pages'] = ' AND page IN (\'' . join( '\', \'', $args['pages'] ) . '\')';
$pages = $args['pages'];
$where = $args['where'];
$limit = $args['limit'];
$dates = $args['dates'];
$sub_where = $args['sub_where'];
$order = sprintf( 'ORDER BY %s %s', $args['orderBy'], $args['order'] );
// phpcs:disable
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT SQL_CALC_FOUND_ROWS t1.page as page, COALESCE( t1.pageviews, 0 ) as pageviews, COALESCE( t1.pageviews - t2.pageviews, 0 ) as difference
FROM ( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$pages}{$dates}{$sub_where} GROUP BY page ) as t1
LEFT JOIN ( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$pages}{$dates}{$sub_where} GROUP BY page ) as t2
ON t1.page = t2.page
{$where}
{$order}
{$limit}",
Stats::get()->start_date,
Stats::get()->end_date,
Stats::get()->compare_start_date,
Stats::get()->compare_end_date
),
ARRAY_A
);
$rowsFound = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
// phpcs:enable
return \compact( 'rows', 'rowsFound' );
}
/**
* Get page views for pages.
*
* @param array $args Array of urls.
*
* @return array
*/
public static function get_pageviews_with_object( $args = [] ) {
global $wpdb;
$args = wp_parse_args(
$args,
[
'order' => 'DESC',
'dates' => ' AND created BETWEEN %s AND %s',
'limit' => '',
'sub_where' => '',
]
);
$order = $args['order'];
$limit = $args['limit'];
$dates = $args['dates'];
$subwhere = $args['sub_where'];
// phpcs:disable
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT SQL_CALC_FOUND_ROWS o.*, COALESCE( traffic.pageviews, 0 ) as pageviews, COALESCE( traffic.difference, 0 ) as difference
FROM {$wpdb->prefix}rank_math_analytics_objects as o
LEFT JOIN (SELECT t1.page as page, COALESCE( t1.pageviews, 0 ) as pageviews, COALESCE( t1.pageviews - t2.pageviews, 0 ) as difference
FROM
( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$dates} GROUP BY page ) as t1
LEFT JOIN
( SELECT page, SUM(pageviews) as pageviews FROM {$wpdb->prefix}rank_math_analytics_ga WHERE 1=1{$dates} GROUP BY page ) as t2
ON t1.page = t2.page ) traffic ON o.page = traffic.page
WHERE o.is_indexable = '1'{$subwhere}
ORDER BY pageviews {$order}
{$limit}",
Stats::get()->start_date,
Stats::get()->end_date,
Stats::get()->compare_start_date,
Stats::get()->compare_end_date
),
ARRAY_A
);
$rowsFound = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
// phpcs:enable
return \compact( 'rows', 'rowsFound' );
}
/**
* Get pageviews for single post by post Id.
*
* @param array $post_ids Post IDs.
*
* @return array
*/
public static function get_traffic_by_object_ids( $post_ids ) {
if ( ! DB_Helper::check_table_exists( 'rank_math_analytics_ga' ) ) {
return [];
}
global $wpdb;
$placeholder = implode( ', ', array_fill( 0, count( $post_ids ), '%d' ) );
// phpcs:disable
$data = $wpdb->get_results(
$wpdb->prepare(
"SELECT t2.object_id, SUM(t1.pageviews) AS traffic FROM {$wpdb->prefix}rank_math_analytics_ga AS t1
Left JOIN {$wpdb->prefix}rank_math_analytics_objects AS t2 ON t1.page=t2.page
WHERE t2.object_id IN ( {$placeholder} ) and t1.created BETWEEN Now() - interval 36 day and Now() - interval 3 day
GROUP BY t2.object_id",
$post_ids
),
ARRAY_A
);
// phpcs:enable
return array_combine( array_column( $data, 'object_id' ), array_column( $data, 'traffic' ) );
}
/**
* Get pageviews for single post by post Id.
*
* @param array $post_ids Post IDs.
*
* @return array
*/
public static function get_impressions_by_object_ids( $post_ids ) {
if ( ! DB_Helper::check_table_exists( 'rank_math_analytics_gsc' ) ) {
return [];
}
global $wpdb;
$placeholder = implode( ', ', array_fill( 0, count( $post_ids ), '%d' ) );
// phpcs:disable
$data = $wpdb->get_results(
$wpdb->prepare(
"SELECT t2.object_id, SUM(impressions) AS traffic FROM {$wpdb->prefix}rank_math_analytics_gsc AS t1
Left JOIN {$wpdb->prefix}rank_math_analytics_objects AS t2 ON t1.page=t2.page
WHERE t2.object_id IN ( {$placeholder} ) and t1.created BETWEEN Now() - interval 36 day and Now() - interval 3 day
GROUP BY t2.object_id",
$post_ids
),
ARRAY_A
);
// phpcs:enable
return array_combine( array_column( $data, 'object_id' ), array_column( $data, 'traffic' ) );
}
}

View File

@@ -0,0 +1,754 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMath
* @subpackage RankMath\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use stdClass;
use WP_Error;
use WP_REST_Request;
use RankMath\Traits\Cache;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
defined( 'ABSPATH' ) || exit;
/**
* Posts class.
*/
class Posts {
use Hooker, Cache;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Posts
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Posts ) ) {
$instance = new Posts();
$instance->setup();
}
return $instance;
}
/**
* Constructor.
*/
public function setup() {
$this->filter( 'rank_math/analytics/single/report', 'add_badges', 10, 1 );
$this->filter( 'rank_math/analytics/single/report', 'add_backlinks', 10, 1 );
$this->filter( 'rank_math/analytics/single/report', 'add_ranking_keywords', 10, 1 );
$this->filter( 'rank_math/analytics/single/report', 'get_graph_data_for_post', 10, 1 );
$this->filter( 'rank_math/analytics/post_data', 'sort_new_data', 10, 2 );
$this->filter( 'rank_math/analytics/get_objects_by_score_args', 'get_objects_by_score_args', 10, 2 );
$this->filter( 'rank_math/analytics/get_posts_rows_by_objects', 'get_posts_rows_by_objects', 10, 2 );
}
/**
* Get posts by objects.
*
* @param boolean $result Check.
* @param WP_REST_Request $request Full details about the request.
* @return $args for order and orderby.
*/
public function get_objects_by_score_args( $result, WP_REST_Request $request ) {
$orderby = $request->get_param( 'orderby' );
$is_valid_order_param = in_array( $orderby, [ 'title', 'seo_score' ], true );
$orderby = $is_valid_order_param ? $orderby : 'created';
$order = strtoupper( $request->get_param( 'order' ) );
$args['orderBy'] = $orderby;
$args['order'] = $order;
return $args;
}
/**
* Change user perference.
*
* @param array $data array.
* @param WP_REST_Request $request post object.
* @return array $data sorted array.
*/
public function sort_new_data( $data, WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
$orderby = $request->get_param( 'orderby' );
$order = strtoupper( $request->get_param( 'order' ) );
if ( 'query' !== $orderby ) {
$data['rankingKeywords'] = $this->ranking_keyword_array_sort( $data['rankingKeywords'], $order, $orderby );
}
if ( 'query' === $orderby ) {
if ( 'DESC' === $order ) {
uasort(
$data['rankingKeywords'],
function( $a, $b ) use ( $orderby ) {
return strtolower( $a[ $orderby ] ) < strtolower( $b[ $orderby ] );
}
);
}
if ( 'ASC' === $order ) {
uasort(
$data['rankingKeywords'],
function( $a, $b ) use ( $orderby ) {
return strtolower( $a[ $orderby ] ) > strtolower( $b[ $orderby ] );
}
);
}
}
return $data;
}
/**
* Sort array for ranking keyword by order and orderby
*
* @param array $arr array.
*
* @param Variable $arr_order is order direction.
*
* @param Variable $arr_orderby is key for sort.
*/
public function ranking_keyword_array_sort( $arr, $arr_order, $arr_orderby ) {
if ( 'DESC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
return $a[ $arr_orderby ]['total'] < $b[ $arr_orderby ]['total'];
}
);
}
if ( 'ASC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
return $a[ $arr_orderby ]['total'] > $b[ $arr_orderby ]['total'];
}
);
}
return $arr;
}
/**
* Get posts by objects.
*
* @param boolean $result Check.
* @param WP_REST_Request $request Full details about the request.
* @return array Posts rows.
*/
public function get_posts_rows_by_objects( $result, WP_REST_Request $request ) {
$orderby = $request->get_param( 'orderby' );
$order = strtoupper( $request->get_param( 'order' ) );
$objects = Stats::get()->get_objects_by_score( $request );
$objects = Links::get_links_by_objects( $objects );
$pages = isset( $objects['rows'] ) ? \array_keys( $objects['rows'] ) : [];
$pageviews = Pageviews::get_pageviews( [ 'pages' => $pages ] );
$pageviews = Stats::get()->set_page_as_key( $pageviews['rows'] );
$console = Stats::get()->get_analytics_data(
[
'orderBy' => 'diffImpressions',
'pageview' => true,
'offset' => 0, // Here offset should always zero.
'perpage' => ! empty( $objects['rowsFound'] ) ? $objects['rowsFound'] : 0,
'sub_where' => " AND page IN ('" . join( "', '", $pages ) . "')",
]
);
$new_rows = [];
if ( ! empty( $objects['rows'] ) ) {
foreach ( $objects['rows'] as $object ) {
$page = $object['page'];
if ( isset( $pageviews[ $page ] ) ) {
$object['pageviews'] = [
'total' => (int) $pageviews[ $page ]['pageviews'],
'difference' => (int) $pageviews[ $page ]['difference'],
];
}
if ( isset( $console[ $page ] ) ) {
$object = \array_merge( $console[ $page ], $object );
}
if ( ! isset( $object['links'] ) ) {
$object['links'] = new stdClass();
}
$new_rows[ $page ] = $object;
}
}
$history = $this->get_graph_data_for_pages( $pages );
$new_rows = Stats::get()->set_page_position_graph( $new_rows, $history );
if ( in_array( $orderby, [ 'position', 'clicks', 'pageviews', 'impressions' ], true ) ) {
$new_rows = $this->analytics_array_sort( $new_rows, $order, $orderby );
}
if ( empty( $new_rows ) ) {
$new_rows['response'] = 'No Data';
}
return [
'rows' => $new_rows,
'rowsFound' => ! empty( $objects['rowsFound'] ) ? $objects['rowsFound'] : 0,
];
}
/**
* Sort array by order and orderby
*
* @param array $arr array.
*
* @param Variable $arr_order is order direction.
*
* @param Variable $arr_orderby is key for sort.
*
* @return $arr sorted array
*/
public function analytics_array_sort( $arr, $arr_order, $arr_orderby ) {
if ( 'DESC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
if ( false === array_key_exists( $arr_orderby, $a ) ) {
$a[ $arr_orderby ] = [ 'total' => '0' ];
}
if ( false === array_key_exists( $arr_orderby, $b ) ) {
$b[ $arr_orderby ] = [ 'total' => '0' ];
}
return $a[ $arr_orderby ]['total'] < $b[ $arr_orderby ]['total'];
}
);
}
if ( 'ASC' === $arr_order ) {
uasort(
$arr,
function( $a, $b ) use ( $arr_orderby ) {
if ( false === array_key_exists( $arr_orderby, $a ) ) {
$a[ $arr_orderby ] = [ 'total' => '0' ];
}
if ( false === array_key_exists( $arr_orderby, $b ) ) {
$b[ $arr_orderby ] = [ 'total' => '0' ];
}
return $a[ $arr_orderby ]['total'] > $b[ $arr_orderby ]['total'];
}
);
}
return $arr;
}
/**
* Get ranking keywords data and append it to existing post data.
*
* @param object $post Post object.
* @return object
*/
public function add_ranking_keywords( $post ) {
$page = $post->page;
$data = Stats::get()->get_analytics_data(
[
'dimension' => 'query',
'offset' => 0,
'perpage' => 20,
'orderBy' => 'impressions',
'sub_where' => "AND page = '{$page}'",
]
);
$history = Keywords::get()->get_graph_data_for_keywords( \array_keys( $data ) );
$post->rankingKeywords = Stats::get()->set_query_position( $data, $history ); // phpcs:ignore
return $post;
}
/**
* Append backlinks data into existing post data.
*
* @param object $post Post object.
* @return object
*/
public function add_backlinks( $post ) {
$post->backlinks = [
'total' => 0,
'previous' => 0,
'difference' => 0,
];
return $post;
}
/**
* Append badges data into existing post data.
*
* @param object $post Post object.
* @return object
*/
public function add_badges( $post ) {
$post->badges = [
'clicks' => $this->get_position_for_badges( 'clicks', $post->page ),
'traffic' => $this->get_position_for_badges( 'traffic', $post->page ),
'keywords' => $this->get_position_for_badges( 'query', $post->page ),
'impressions' => $this->get_position_for_badges( 'impressions', $post->page ),
];
return $post;
}
/**
* Get position for badges.
*
* @param string $column Column name.
* @param string $page Page url.
* @return integer
*/
public function get_position_for_badges( $column, $page ) {
$start = date( 'Y-m-d H:i:s', strtotime( '-30 days ', Stats::get()->end ) );
if ( 'traffic' === $column ) {
$rows = DB::traffic()
->select( 'page' )
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ $start, Stats::get()->end_date ] )
->groupBy( 'page' )
->orderBy( 'pageviews', 'DESC' )
->limit( 5 );
} else {
$rows = DB::analytics()
->select( 'page' )
->whereBetween( 'created', [ $start, Stats::get()->end_date ] )
->groupBy( 'page' )
->orderBy( $column, 'DESC' )
->limit( 5 );
}
if ( 'impressions' === $column || 'click' === $column ) {
$rows->selectSum( $column, $column );
}
if ( 'query' === $column ) {
$rows->selectCount( 'DISTINCT(query)', 'keywords' );
}
$rows = $rows->get( ARRAY_A );
foreach ( $rows as $index => $row ) {
if ( $page === $row['page'] ) {
return $index + 1;
}
}
return 99;
}
/**
* Append analytics graph data into existing post data.
*
* @param object $post Post object.
* @return object
*/
public function get_graph_data_for_post( $post ) {
global $wpdb;
// Step1. Get splitted date intervals for graph within selected date range.
$data = new stdClass();
$page = $post->page;
$intervals = Stats::get()->get_intervals();
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
// Step2. Get analytics data summary for each splitted date intervals.
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM( clicks ) as clicks, SUM(impressions) as impressions, ROUND( AVG(ctr), 2 ) as ctr, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$metrics = $wpdb->get_results( $query );
// Step3. Get position data summary for each splitted date intervals.
$query = $wpdb->prepare(
"SELECT page, MAX(CONCAT(t.uid, ':', t.range_group)) as range_group FROM
(SELECT page, MAX(CONCAT(page, ':', DATE(created), ':', LPAD((100 - position), 3, '0'))) as uid, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group, DATE(created)
ORDER BY DATE(created) DESC) AS t
GROUP BY t.range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$positions = $wpdb->get_results( $query );
$positions = Stats::get()->extract_data_from_mixed( $positions, 'range_group', ':', [ 'range_group', 'position', 'date' ] );
// Step4. Get keywords count for each splitted date intervals.
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, COUNT(DISTINCT(query)) as keywords, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$keywords = $wpdb->get_results( $query );
// phpcs:enable
// Step5. Filter graph data.
$metrics = Stats::get()->filter_graph_rows( $metrics );
$positions = Stats::get()->filter_graph_rows( $positions );
$keywords = Stats::get()->filter_graph_rows( $keywords );
// Step6. Convert types.
$metrics = array_map( [ Stats::get(), 'normalize_graph_rows' ], $metrics );
$positions = array_map( [ Stats::get(), 'normalize_graph_rows' ], $positions );
$keywords = array_map( [ Stats::get(), 'normalize_graph_rows' ], $keywords );
// Step7. Merge all analytics data.
$data = Stats::get()->get_date_array(
$intervals['dates'],
[
'clicks' => [],
'impressions' => [],
'position' => [],
'ctr' => [],
'keywords' => [],
'pageviews' => [],
]
);
$data = Stats::get()->get_merge_data_graph( $metrics, $data, $intervals['map'] );
$data = Stats::get()->get_merge_data_graph( $positions, $data, $intervals['map'] );
$data = Stats::get()->get_merge_data_graph( $keywords, $data, $intervals['map'] );
// Step8. Get traffic data in case analytics is connected for each splitted data intervals.
if ( \RankMath\Google\Analytics::is_analytics_connected() ) {
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM( pageviews ) as pageviews, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_ga
WHERE created BETWEEN %s AND %s AND page LIKE '%{$page}'
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$traffic = $wpdb->get_results( $query );
// Filter graph data.
$traffic = Stats::get()->filter_graph_rows( $traffic );
// Convert types.
$traffic = array_map( [ Stats::get(), 'normalize_graph_rows' ], $traffic );
$data = Stats::get()->get_merge_data_graph( $traffic, $data, $intervals['map'] );
}
$data = Stats::get()->get_graph_data_flat( $data );
// Step9. Append graph data into existing post data.
$post->graph = array_values( $data );
return $post;
}
/**
* Get posts rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array Posts rows.
*/
public function get_posts_rows( WP_REST_Request $request ) {
$per_page = 25;
$cache_args = $request->get_params();
$cache_args['per_page'] = $per_page;
$cache_group = 'rank_math_rest_posts_rows';
$cache_key = $this->generate_hash( $cache_args );
$data = $this->get_cache( $cache_key, $cache_group );
if ( ! empty( $data ) ) {
return $data;
}
// Pagination.
$offset = ( $request->get_param( 'page' ) - 1 ) * $per_page;
$orderby = $request->get_param( 'orderby' );
$post_type = sanitize_key( $request->get_param( 'postType' ) );
$order = $request->get_param( 'order' );
$order = in_array( $order, [ 'asc', 'desc' ], true ) ? $order : 'desc';
$order = strtoupper( $order );
$post_type_clause = $post_type ? " AND o.object_subtype = '{$post_type}'" : '';
if ( 'pageviews' === $orderby ) {
// Get posts order by pageviews.
$t_data = Pageviews::get_pageviews_with_object(
[
'order' => $order,
'limit' => "LIMIT {$offset}, {$per_page}",
'sub_where' => $post_type_clause,
]
);
$pageviews = Stats::get()->set_page_as_key( $t_data['rows'] );
$pages = \array_keys( $pageviews );
$pages = array_map( 'esc_sql', $pages );
$console = Stats::get()->get_analytics_data(
[
'offset' => 0, // Should set as 0.
'perpage' => $per_page,
'objects' => false,
'sub_where' => " AND page IN ('" . join( "', '", $pages ) . "')",
]
);
$data['rowsFound'] = $this->rows_found();
foreach ( $pageviews as $page => &$pageview ) {
$pageview['pageviews'] = [
'total' => (int) $pageview['pageviews'],
'difference' => (int) $pageview['difference'],
];
if ( isset( $console[ $page ] ) ) {
unset( $console[ $page ]['pageviews'] );
$pageview = \array_merge( $pageview, $console[ $page ] );
}
}
$history = $this->get_graph_data_for_pages( $pages );
$pageviews = Stats::get()->set_page_position_graph( $pageviews, $history );
$data['rows'] = $pageviews;
} else {
// Get posts order by impressions.
$t_data = DB::objects()
->select( [ 'page', 'title', 'object_id' ] )
->where( 'is_indexable', 1 );
if ( 'title' === $orderby ) {
$t_data->orderBy( $orderby, $order )
->limit( $per_page, $offset );
}
$t_data = $t_data->get( ARRAY_A );
$pages = Stats::get()->set_page_as_key( $t_data );
$params = \array_keys( $pages );
$params = array_map( 'esc_sql', $params );
$args = [
'dimension' => 'page',
'offset' => 0,
'perpage' => 20000,
'sub_where' => " AND page IN ('" . join( "', '", $params ) . "')",
];
if ( 'title' !== $orderby ) {
$args['orderBy'] = $orderby;
$args['order'] = $order;
}
$rows = Stats::get()->get_analytics_data( $args );
if ( 'title' !== $orderby ) {
foreach ( $pages as $page => $row ) {
if ( ! isset( $rows[ $page ] ) ) {
$rows[ $page ] = $row;
} else {
$rows[ $page ] = \array_merge( $rows[ $page ], $row );
}
}
$history = $this->get_graph_data_for_pages( $params );
$data['rows'] = Stats::get()->set_page_position_graph( $rows, $history );
$data['rowsFound'] = count( $pages );
// Filter array by $offset, $perpage value.
$data['rows'] = array_slice( $data['rows'], $offset, $per_page, true );
} else {
foreach ( $pages as $page => &$row ) {
if ( isset( $rows[ $page ] ) ) {
$row = \array_merge( $row, $rows[ $page ] );
}
}
$history = $this->get_graph_data_for_pages( $params );
$data['rows'] = Stats::get()->set_page_position_graph( $pages, $history );
$data['rowsFound'] = $this->rows_found();
}
// Get fetched page info again.
$pages = Stats::get()->set_page_as_key( $data['rows'] );
$params = \array_keys( $pages );
$params = array_map( 'esc_sql', $params );
// Get pageviews info.
$pageviews = Pageviews::get_pageviews_with_object(
[
'limit' => "LIMIT 0, {$per_page}",
'sub_where' => " AND o.page IN ('" . join( "', '", $params ) . "')" . $post_type_clause,
]
);
$pageviews = Stats::get()->set_page_as_key( $pageviews['rows'] );
// Merge pageview info into main data.
foreach ( $data['rows'] as $page => &$row ) {
if ( isset( $pageviews[ $page ] ) ) {
$pageview = [
'pageviews' => [
'total' => (int) $pageviews[ $page ]['pageviews'],
'difference' => (int) $pageviews[ $page ]['difference'],
],
];
$row = \array_merge( $row, $pageview );
}
}
}
if ( empty( $data ) ) {
$data['response'] = 'No Data';
} else {
$this->set_cache( $cache_key, $data, $cache_group, DAY_IN_SECONDS );
}
return $data;
}
/**
* Get top 5 winning posts.
*
* @return array
*/
public function get_winning_posts() {
global $wpdb;
$cache_key = Stats::get()->get_cache_key( 'winning_posts', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
$rows = Stats::get()->get_analytics_data(
[
'order' => 'ASC',
'objects' => true,
'pageview' => true,
'offset' => 0,
'perpage' => 5,
'type' => 'win',
]
);
$history = $this->get_graph_data_for_pages( \array_keys( $rows ) );
$rows = Stats::get()->set_page_position_graph( $rows, $history );
if ( empty( $rows ) ) {
$rows['response'] = 'No Data';
}
set_transient( $cache_key, $rows, DAY_IN_SECONDS );
return $rows;
}
/**
* Get top 5 losing posts.
*
* @return object
*/
public function get_losing_posts() {
global $wpdb;
$cache_key = Stats::get()->get_cache_key( 'losing_posts', Stats::get()->days . 'days' );
$cache = get_transient( $cache_key );
if ( false !== $cache ) {
return $cache;
}
$rows = Stats::get()->get_analytics_data(
[
'objects' => true,
'pageview' => true,
'offset' => 0,
'perpage' => 5,
'type' => 'lose',
]
);
$history = $this->get_graph_data_for_pages( \array_keys( $rows ) );
$rows = Stats::get()->set_page_position_graph( $rows, $history );
if ( empty( $rows ) ) {
$rows['response'] = 'No Data';
}
set_transient( $cache_key, $rows, DAY_IN_SECONDS );
return $rows;
}
/**
* Get graph data for pages.
*
* @param array $pages Pages to get data for.
*
* @return array
*/
public function get_graph_data_for_pages( $pages ) {
global $wpdb;
$intervals = Stats::get()->get_intervals();
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$pages = \array_map( 'esc_sql', $pages );
$pages = '(\'' . join( '\', \'', $pages ) . '\')';
$query = $wpdb->prepare(
"SELECT page, date, MAX(CONCAT(t.uid, ':', t.range_group)) as range_group FROM
( SELECT page, DATE_FORMAT( created,'%%Y-%%m-%%d') as date, MAX( CONCAT( page, ':', DATE( created ), ':', LPAD( ( 100 - position ), 3, '0' ) ) ) as uid, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_gsc
WHERE page IN {$pages} AND created BETWEEN %s AND %s
GROUP BY page, range_group, DATE(created)
ORDER BY page ASC, DATE(created) DESC) AS t
GROUP BY t.page, t.range_group
ORDER BY date ASC",
Stats::get()->start_date,
Stats::get()->end_date
);
$data = $wpdb->get_results( $query );
$data = Stats::get()->extract_data_from_mixed( $data, 'range_group', ':', [ 'range_group', 'position' ] );
$data = Stats::get()->filter_graph_rows( $data );
return array_map( [ Stats::get(), 'normalize_graph_rows' ], $data );
}
/**
* Count indexable pages.
*
* @return mixed
*/
private function rows_found() {
return DB::objects()
->selectCount( 'page' )
->where( 'is_indexable', 1 )
->getVar();
}
}

View File

@@ -0,0 +1,535 @@
<?php
/**
* The Global functionality of the plugin.
*
* Defines the functionality loaded on admin.
*
* @since 1.0.15
* @package RankMathPro
* @subpackage RankMathPro\Rest
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use WP_Error;
use WP_REST_Server;
use RankMath\Helper;
use WP_REST_Request;
use WP_REST_Controller;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Google\PageSpeed;
use RankMath\SEO_Analysis\SEO_Analyzer;
use RankMathPro\Analytics\DB;
use MyThemeShop\Helpers\DB as DB_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Rest class.
*/
class Rest extends WP_REST_Controller {
/**
* Constructor.
*/
public function __construct() {
$this->namespace = \RankMath\Rest\Rest_Helper::BASE . '/an';
}
/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/getKeywordPages',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ Keywords::get(), 'get_keyword_pages' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/postsOverview',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_posts_overview' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getTrackedKeywords',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keywords' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getTrackedKeywordsRows',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keywords_rows' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getTrackedKeywordSummary',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keyword_summary' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/trackedKeywordsOverview',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_tracked_keywords_overview' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/addTrackKeyword',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'add_track_keyword' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/autoAddFocusKeywords',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'auto_add_focus_keywords' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/removeTrackKeyword',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'remove_track_keyword' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/deleteTrackedKeywords',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'delete_all_tracked_keywords' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/getPagespeed',
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'get_pagespeed' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/postsRows',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ Posts::get(), 'get_posts_rows' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
register_rest_route(
$this->namespace,
'/inspectionStats',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_inspection_stats' ],
'permission_callback' => [ $this, 'has_permission' ],
]
);
}
/**
* Determines if the current user can manage analytics.
*
* @return true
*/
public function has_permission() {
return current_user_can( 'rank_math_analytics' );
}
/**
* Get top 5 winning and losing posts rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_posts_overview( WP_REST_Request $request ) {
return rest_ensure_response(
[
'winningPosts' => Posts::get()->get_winning_posts(),
'losingPosts' => Posts::get()->get_losing_posts(),
]
);
}
/**
* Get tracked keywords rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_tracked_keywords( WP_REST_Request $request ) {
return rest_ensure_response(
[ 'rows' => Keywords::get()->get_tracked_keywords() ]
);
}
/**
* Get tracked keywords rows.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array
*/
public function get_tracked_keywords_rows( WP_REST_Request $request ) {
return Keywords::get()->get_tracked_keywords_rows( $request );
}
/**
* Get tracked keywords summary.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_tracked_keyword_summary( WP_REST_Request $request ) {
\RankMathPro\Admin\Api::get()->get_settings();
return rest_ensure_response( Keywords::get()->get_tracked_keywords_summary() );
}
/**
* Get top 5 winning and losing tracked keywords overview.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_tracked_keywords_overview( WP_REST_Request $request ) {
return rest_ensure_response(
[
'winningKeywords' => Keywords::get()->get_tracked_winning_keywords(),
'losingKeywords' => Keywords::get()->get_tracked_losing_keywords(),
]
);
}
/**
* Add track keyword to DB.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function auto_add_focus_keywords( WP_REST_Request $request ) {
$data = $request->get_param( 'data' );
$secondary_keyword = ! empty( $data['secondary_keyword'] );
$post_types = ! empty( $data['post_types'] ) ? $data['post_types'] : [];
$all_opts = rank_math()->settings->all_raw();
$general = $all_opts['general'];
$general['auto_add_focus_keywords'] = $data;
Helper::update_all_settings( $general, null, null );
if ( empty( $post_types ) ) {
return false;
}
global $wpdb;
$focus_keywords = $wpdb->get_col(
"SELECT {$wpdb->postmeta}.meta_value FROM {$wpdb->posts} INNER JOIN {$wpdb->postmeta}
ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id
WHERE 1=1
AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_types ) ) . "')
AND {$wpdb->posts}.post_status = 'publish'
AND {$wpdb->postmeta}.meta_key = 'rank_math_focus_keyword'
"
);
$keywords_data = [];
foreach ( $focus_keywords as $focus_keyword ) {
$keywords = explode( ',', mb_strtolower( $focus_keyword ) );
if ( $secondary_keyword ) {
$keywords_data = array_merge( $keywords, $keywords_data );
} else {
$keywords_data[] = current( $keywords );
}
}
if ( empty( $keywords_data ) ) {
return false;
}
return DB::bulk_insert_query_focus_keyword_data( array_unique( $keywords_data ) );
}
/**
* Add track keyword to DB.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function add_track_keyword( WP_REST_Request $request ) {
$keywords = $request->get_param( 'keyword' );
$keywords = mb_strtolower( filter_var( $keywords, FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES ) );
if ( empty( $keywords ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no keyword found.', 'rank-math-pro' )
);
}
$keywords = html_entity_decode( $keywords );
// Check remain keywords count can be added.
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$new_keywords = Keywords::get()->extract_addable_track_keyword( $keywords );
$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 keywords.
Keywords::get()->add_track_keyword( $new_keywords );
$registered = Admin_Helper::get_registration_data();
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
// Send total keywords count to RankMath.
$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 true;
}
/**
* Remove track keyword from DB.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function remove_track_keyword( WP_REST_Request $request ) {
$keyword = $request->get_param( 'keyword' );
if ( empty( $keyword ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no keyword found.', 'rank-math-pro' )
);
}
// Remove keyword.
Keywords::get()->remove_track_keyword( $keyword );
$registered = Admin_Helper::get_registration_data();
if ( ! $registered || empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
// Send total keywords count to RankMath.
$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 true;
}
/**
* Delete all the manually tracked keywords.
*/
public function delete_all_tracked_keywords() {
// Delete all keywords.
Keywords::get()->delete_all_tracked_keywords();
$registered = Admin_Helper::get_registration_data();
if ( empty( $registered['username'] ) || empty( $registered['api_key'] ) ) {
return false;
}
// Send total keywords count as 0 to RankMath.
$response = \RankMathPro\Admin\Api::get()->keywords_info( $registered['username'], $registered['api_key'], 0 );
if ( $response ) {
update_option( 'rank_math_keyword_quota', $response );
}
return true;
}
/**
* Check if keyword can be added.
*
* @param string $keywords Comma separated keywords.
* @return bool True if remain keyword count is larger than zero.
*/
private function can_add_keyword( $keywords = '' ) {
// Check remain keywords count can be added by supposing current keyword is added.
$total_keywords = Keywords::get()->get_tracked_keywords_count();
$new_keywords = Keywords::get()->extract_addable_track_keyword( $keywords );
$keywords_count = count( $new_keywords );
$summary = Keywords::get()->get_tracked_keywords_quota();
$remain = $summary['available'] - $total_keywords - $keywords_count;
return $remain >= 0;
}
/**
* Get page speed data.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return array|bool Pagespeed info on success, false on failure.
*/
public function get_pagespeed( WP_REST_Request $request ) {
$id = $request->get_param( 'id' );
if ( empty( $id ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no record id found.', 'rank-math-pro' )
);
}
$post_id = $request->get_param( 'objectID' );
if ( empty( $id ) ) {
return new WP_Error(
'param_value_empty',
esc_html__( 'Sorry, no post id found.', 'rank-math-pro' )
);
}
if ( Helper::is_localhost() ) {
return [
'page_score' => 0,
'desktop_interactive' => 0,
'desktop_pagescore' => 0,
'mobile_interactive' => 0,
'mobile_pagescore' => 0,
'pagespeed_refreshed' => current_time( 'mysql' ),
];
}
$url = get_permalink( $post_id );
$pre = apply_filters( 'rank_math/analytics/pre_pagespeed', false, $post_id, $request );
if ( false !== $pre ) {
return $pre;
}
$force = \boolval( $request->get_param( 'force' ) );
$is_admin_bar = \boolval( $request->get_param( 'isAdminBar' ) );
if ( $force || ( ! $is_admin_bar && $this->should_update_pagespeed( $id ) ) ) {
// Page Score.
$analyzer = new SEO_Analyzer();
$analyzer->set_url();
$score = $analyzer->get_page_score( $url );
$update = [];
if ( $score > 0 ) {
$update['page_score'] = $score;
}
// PageSpeed desktop.
$desktop = PageSpeed::get_pagespeed( $url, 'desktop' );
if ( ! empty( $desktop ) ) {
$update = \array_merge( $update, $desktop );
$update['pagespeed_refreshed'] = current_time( 'mysql' );
}
// PageSpeed mobile.
$mobile = PageSpeed::get_pagespeed( $url, 'mobile' );
if ( ! empty( $mobile ) ) {
$update = \array_merge( $update, $mobile );
$update['pagespeed_refreshed'] = current_time( 'mysql' );
}
}
if ( ! empty( $update ) ) {
$update['id'] = $id;
$update['object_id'] = $post_id;
DB::update_object( $update );
}
return empty( $update ) ? false : $update;
}
/**
* Should update pagespeed record.
*
* @param int $id Database row id.
* @return bool
*/
private function should_update_pagespeed( $id ) {
$record = DB::objects()->where( 'id', $id )->one();
return \time() > ( \strtotime( $record->pagespeed_refreshed ) + ( DAY_IN_SECONDS * 7 ) );
}
/**
* Get inspection stats.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_inspection_stats() {
// Early Bail!!
if ( ! DB_Helper::check_table_exists( 'rank_math_analytics_inspections' ) ) {
return [
'presence' => [],
'status' => [],
];
}
return rest_ensure_response(
[
'presence' => Url_Inspection::get_presence_stats(),
'status' => Url_Inspection::get_status_stats(),
]
);
}
}

View File

@@ -0,0 +1,285 @@
<?php
/**
* The Analytics Module
*
* @since 2.0.0
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMath\Analytics\Stats;
use RankMath\Admin\Admin_Helper;
use RankMathPro\Admin\Admin_Helper as ProAdminHelper;
use MyThemeShop\Helpers\DB as DB_Helper;
defined( 'ABSPATH' ) || exit;
/**
* Summary class.
*/
class Summary {
use Hooker;
/**
* Constructor.
*/
public function __construct() {
if ( \RankMathPro\Google\Adsense::is_adsense_connected() ) {
$this->filter( 'rank_math/analytics/summary', 'get_adsense_summary' );
}
if ( \RankMath\Google\Analytics::is_analytics_connected() ) {
$this->filter( 'rank_math/analytics/summary', 'get_pageviews_summary' );
$this->filter( 'rank_math/analytics/get_widget', 'get_pageviews_summary' );
}
$this->filter( 'rank_math/analytics/summary', 'get_clicks_summary' );
$this->filter( 'rank_math/analytics/summary', 'get_g_update_summary' );
$this->filter( 'rank_math/analytics/posts_summary', 'get_posts_summary', 10, 3 );
$this->filter( 'rank_math/analytics/analytics_summary_graph', 'get_analytics_summary_graph', 10, 2 );
$this->filter( 'rank_math/analytics/analytics_tables_info', 'get_analytics_tables_info' );
}
/**
* Get posts summary.
*
* @param object $summary Posts summary.
* @param string $post_type Post type.
* @param string $query Query to get the summary data.
* @return object
*/
public function get_posts_summary( $summary, $post_type, $query ) {
if ( empty( $summary ) ) {
return $summary;
}
if ( $post_type && is_string( $post_type ) ) {
global $wpdb;
$query->leftJoin( $wpdb->prefix . 'rank_math_analytics_objects', $wpdb->prefix . 'rank_math_analytics_gsc.page', $wpdb->prefix . 'rank_math_analytics_objects.page' );
$query->where( $wpdb->prefix . 'rank_math_analytics_objects.object_subtype', sanitize_key( $post_type ) );
$summary = (object) $query->one();
}
$summary->pageviews = DB::traffic()
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
return $summary;
}
/**
* Get pageviews summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_pageviews_summary( $stats ) {
$pageviews = DB::traffic()
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
$old_pageviews = DB::traffic()
->selectSum( 'pageviews', 'pageviews' )
->whereBetween( 'created', [ Stats::get()->compare_start_date, Stats::get()->compare_end_date ] )
->getVar();
$stats->pageviews = [
'total' => (int) $pageviews,
'previous' => (int) $old_pageviews,
'difference' => (int) $pageviews - (int) $old_pageviews,
];
return $stats;
}
/**
* Get adsense summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_adsense_summary( $stats ) {
$stats->adsense = [
'total' => 0,
'previous' => 0,
'difference' => 0,
];
if ( DB_Helper::check_table_exists( 'rank_math_analytics_adsense' ) ) {
$earnings = DB::adsense()
->selectSum( 'earnings', 'earnings' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
$old_earnings = DB::adsense()
->selectSum( 'earnings', 'earnings' )
->whereBetween( 'created', [ Stats::get()->compare_start_date, Stats::get()->compare_end_date ] )
->getVar();
$stats->adsense = [
'total' => (int) $earnings,
'previous' => (int) $old_earnings,
'difference' => (int) $earnings - (int) $old_earnings,
];
}
return $stats;
}
/**
* Get analytics and adsense graph data.
*
* @param object $data Graph data.
* @param array $intervals Date intervals.
* @return array
*/
public function get_analytics_summary_graph( $data, $intervals ) {
global $wpdb;
if ( \RankMath\Google\Analytics::is_analytics_connected() ) {
$data->traffic = $this->get_traffic_graph( $intervals );
// Convert types.
$data->traffic = array_map( [ Stats::get(), 'normalize_graph_rows' ], $data->traffic );
// Merge for performance.
$data->merged = Stats::get()->get_merge_data_graph( $data->traffic, $data->merged, $intervals['map'] );
}
if ( \RankMathPro\Google\Adsense::is_adsense_connected() ) {
$data->adsense = $this->get_adsense_graph( $intervals );
// Convert types.
$data->adsense = array_map( [ Stats::get(), 'normalize_graph_rows' ], $data->adsense );
// Merge for performance.
$data->merged = Stats::get()->get_merge_data_graph( $data->adsense, $data->merged, $intervals['map'] );
}
return $data;
}
/**
* Get analytics graph data.
*
* @param array $intervals Date intervals.
* @return array
*/
public function get_traffic_graph( $intervals ) {
global $wpdb;
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM(pageviews) as pageviews, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_ga
WHERE created BETWEEN %s AND %s
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$traffic_data = $wpdb->get_results( $query );
// phpcs:enable
return $traffic_data;
}
/**
* Get adsense graph data.
*
* @param array $intervals Date intervals.
* @return array
*/
public function get_adsense_graph( $intervals ) {
global $wpdb;
$adsense_data = [];
if ( DB_Helper::check_table_exists( 'rank_math_analytics_adsense' ) ) {
$sql_daterange = Stats::get()->get_sql_date_intervals( $intervals );
$query = $wpdb->prepare(
"SELECT DATE_FORMAT( created, '%%Y-%%m-%%d') as date, SUM(earnings) as earnings, {$sql_daterange}
FROM {$wpdb->prefix}rank_math_analytics_adsense
WHERE created BETWEEN %s AND %s
GROUP BY range_group",
Stats::get()->start_date,
Stats::get()->end_date
);
$adsense_data = $wpdb->get_results( $query );
// phpcs:enable
}
return $adsense_data;
}
/**
* Get clicks summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_clicks_summary( $stats ) {
$clicks = DB::analytics()
->selectSum( 'clicks', 'clicks' )
->whereBetween( 'created', [ Stats::get()->start_date, Stats::get()->end_date ] )
->getVar();
$old_clicks = DB::analytics()
->selectSum( 'clicks', 'clicks' )
->whereBetween( 'created', [ Stats::get()->compare_start_date, Stats::get()->compare_end_date ] )
->getVar();
$stats->clicks = [
'total' => (int) $clicks,
'previous' => (int) $old_clicks,
'difference' => $clicks - $old_clicks,
];
return $stats;
}
/**
* Get google update summary.
*
* @param object $stats Stats holder.
* @return object
*/
public function get_g_update_summary( $stats ) {
if ( ! Helper::get_settings( 'general.google_updates' ) && ProAdminHelper::is_business_plan() ) {
$stats->graph->g_updates = null;
return $stats;
}
$stored = get_site_option( 'rank_math_pro_google_updates' );
$g_updates = json_decode( $stored );
$stats->graph->g_updates = $g_updates;
return $stats;
}
/**
* Get analytics tables info
*
* @param array $data Analytics tables info.
* @return array
*/
public function get_analytics_tables_info( $data ) {
$pro_data = DB::info();
$days = $data['days'] + $pro_data['days'];
$rows = $data['rows'] + $pro_data['rows'];
$size = $data['size'] + $pro_data['size'];
$data = compact( 'days', 'rows', 'size' );
return $data;
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* URL Inspection features.
*
* @since 3.0.8
* @package RankMathPro
* @subpackage RankMathPro
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMathPro\Analytics\DB;
/**
* Url_Inspection class.
*/
class Url_Inspection {
use Hooker;
/**
* The Constructor.
*/
public function __construct() {
$this->filter( 'rank_math/analytics/url_inspection_map_properties', 'map_inspection_properties', 10, 2 );
$this->action( 'rank_math/analytics/get_inspections_query', 'add_filter_params', 10, 2 );
$this->action( 'rank_math/analytics/get_inspections_count_query', 'add_filter_params', 10, 2 );
$this->filter( 'rank_math/analytics/post_data', 'add_index_verdict_data', 10, 2 );
// Enqueue.
$this->action( 'rank_math/admin/enqueue_scripts', 'enqueue_scripts' );
}
/**
* Filter to alter the where clause used in the get_inspections function.
*
* @param string $where WHERE clause.
* @param array $params Parameters.
*
* @return string
*/
public function add_filter_params( $query, $params ) {
if ( empty( $params['indexingFilter'] ) ) {
return;
}
$table = DB::inspections()->table;
$query->where( "$table.coverage_state", $params['indexingFilter'] );
}
/**
* Map properties in the API result to columns in the database.
*
* @param array $normalized Normalized data.
* @param array $incoming Incoming data from the API.
*
* @return array
*/
public function map_inspection_properties( $normalized, $incoming ) {
$handler = \RankMath\Google\Url_Inspection::get();
$handler->assign_inspection_value( $incoming, 'richResultsResult.detectedItems', 'rich_results_items', $normalized );
$handler->assign_inspection_value( $incoming, 'indexStatusResult.lastCrawlTime', 'last_crawl_time', $normalized );
// Store the raw response, too.
$normalized['raw_api_response'] = wp_json_encode( $incoming );
return $normalized;
}
/**
* Get stats for "Presence on Google" widget.
*/
public static function get_presence_stats() {
return DB::get_presence_stats();
}
/**
* Get stats for "Top Statuses" widget.
*/
public static function get_status_stats() {
return DB::get_status_stats();
}
/**
* Change user perference.
*
* @param array $data array.
* @param WP_REST_Request $request post object.
* @return array $data sorted array.
*/
public function add_index_verdict_data( $data, \WP_REST_Request $request ) {
if ( ! Helper::can_add_index_status() ) {
return $data;
}
$data['indexStatus'] = DB::get_index_verdict( $data['page'] );
return $data;
}
/**
* Enqueue scripts.
*/
public function enqueue_scripts() {
$screen = get_current_screen();
if ( 'rank-math_page_rank-math-analytics' !== $screen->id ) {
return;
}
$submit_url = add_query_arg(
[
'page' => 'instant-indexing',
'tab' => 'console',
'apiaction' => 'update',
'_wpnonce' => wp_create_nonce( 'giapi-action' ),
'apipostid[]' => '',
],
admin_url( 'admin.php' )
);
$settings = get_option( 'rank-math-options-instant-indexing', [] );
Helper::add_json(
'instantIndexingSupport',
[
'isPluginActive' => is_plugin_active( 'fast-indexing-api/instant-indexing.php' ),
'isGoogleConfigured' => ! empty( $settings['json_key'] ),
'submitUrl' => $submit_url,
]
);
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* Google AdSense.
*
* @since 1.0.34
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Google;
use RankMath\Google\Api;
use RankMath\Helpers\Security;
use RankMath\Analytics\Workflow\Base;
use WP_Error;
defined( 'ABSPATH' ) || exit;
/**
* AdSense class.
*/
class Adsense {
/**
* Get adsense accounts.
*
* @return array
*/
public static function get_adsense_accounts() {
$accounts = [];
$response = Api::get()->http_get( 'https://adsense.googleapis.com/v2/accounts' );
if (
! Api::get()->is_success() ||
isset( $response->error ) ||
! isset( $response['accounts'] ) ||
! is_array( $response['accounts'] )
) {
return $accounts;
}
foreach ( $response['accounts'] as $account ) {
$accounts[ $account['name'] ] = [
'name' => $account['displayName'],
];
}
return $accounts;
}
/**
* Query adsense data from google client api.
*
* @param string $start_date Start date.
* @param string $end_date End date.
*
* @return array
*/
public static function get_adsense( $options = [] ) {
$account_id = isset( $options['account_id'] ) ? $options['account_id'] : self::get_adsense_id();
$start_date = isset( $options['start_date'] ) ? $options['start_date'] : '';
$end_date = isset( $options['end_date'] ) ? $options['end_date'] : '';
if ( ! $account_id || ! $start_date || ! $end_date ) {
return false;
}
$request = Security::add_query_arg_raw(
[
'startDate.year' => gmdate( 'Y', strtotime( $start_date ) ),
'startDate.month' => gmdate( 'n', strtotime( $start_date ) ),
'startDate.day' => gmdate( 'j', strtotime( $start_date ) ),
'endDate.year' => gmdate( 'Y', strtotime( $end_date ) ),
'endDate.month' => gmdate( 'n', strtotime( $end_date ) ),
'endDate.day' => gmdate( 'j', strtotime( $end_date ) ),
'dimensions' => 'DATE',
'currencyCode' => 'USD',
'metrics' => 'ESTIMATED_EARNINGS',
],
'https://adsense.googleapis.com/v2/' . $account_id . '/reports:generate'
);
$workflow = 'adsense';
Api::get()->set_workflow( $workflow );
$response = Api::get()->http_get( $request );
Api::get()->log_failed_request( $response, $workflow, $start_date, func_get_args() );
if ( ! Api::get()->is_success() ) {
return new WP_Error( 'adsense_api_fail', 'Google AdSense API request failed.' );
}
if ( ! isset( $response['rows'] ) ) {
return false;
}
return $response['rows'];
}
/**
* Get adsense id.
*
* @return string
*/
public static function get_adsense_id() {
static $rank_math_adsense_id;
if ( is_null( $rank_math_adsense_id ) ) {
$options = get_option( 'rank_math_google_analytic_options' );
$rank_math_adsense_id = ! empty( $options['adsense_id'] ) ? $options['adsense_id'] : false;
}
return $rank_math_adsense_id;
}
/**
* Is adsense connected.
*
* @return boolean
*/
public static function is_adsense_connected() {
$account = wp_parse_args(
get_option( 'rank_math_google_analytic_options' ),
[ 'adsense_id' => '' ]
);
return ! empty( $account['adsense_id'] );
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Google PageSpeed.
*
* @since 1.0.34
* @package RankMath
* @subpackage RankMath\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Google;
use RankMath\Google\Api;
use RankMath\Helpers\Security;
defined( 'ABSPATH' ) || exit;
/**
* PageSpeed class.
*/
class PageSpeed {
/**
* Get pagespeed score info.
*
* @param string $url Url to get pagespeed for.
* @param string $strategy Data for desktop or mobile.
*
* @return array
*/
public static function get_pagespeed( $url, $strategy = 'desktop' ) {
$response = Api::get()->http_get( 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=PERFORMANCE&url=' . \rawurlencode( $url ) . '&strategy=' . \strtoupper( $strategy ), [], 30 );
if ( ! Api::get()->is_success() ) {
return false;
}
return [
$strategy . '_interactive' => round( \floatval( $response['lighthouseResult']['audits']['interactive']['displayValue'] ), 0 ),
$strategy . '_pagescore' => round( $response['lighthouseResult']['categories']['performance']['score'] * 100, 0 ),
];
}
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,44 @@
<?php
/**
* Analytics Report header template.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMath\Helper;
defined( 'ABSPATH' ) || exit;
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-info">
<tr>
<td>
<h1><?php esc_html_e( 'SEO Report of Your Website', 'rank-math-pro' ); ?></h1>
###TOP_HTML###
<h2 class="report-date">###START_DATE### - ###END_DATE###</h2>
<a href="###SITE_URL###" target="_blank" class="site-url">###SITE_URL_SIMPLE###</a>
</td>
<?php if ( $this->get_setting( 'link_full_report', true ) ) : ?>
<td class="full-report-link">
<a href="###REPORT_URL###" target="_blank" class="full-report-link">
<?php esc_html_e( 'FULL REPORT', 'rank-math-pro' ); ?>
<?php $this->image( 'report-icon-external.png', 12, 12, __( 'External Link Icon', 'rank-math-pro' ) ); ?>
</a>
</td>
<?php endif; ?>
</tr>
</table>
<?php if ( $this->get_variable( 'stats_invalid_data' ) ) { ?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-error">
<tr>
<td>
<h2><?php esc_html_e( 'Uh-oh', 'rank-math-pro' ); ?></h2>
<p><em><?php esc_html_e( 'It seems that there are no stats to show right now.', 'rank-math-pro' ); ?></em></p>
<?php // Translators: placeholders are anchor opening and closing tags. ?>
<p><?php printf( esc_html__( 'If you can see the site data in your Search Console and Analytics accounts, but not here, then %1$s try reconnecting your account %2$s and make sure that the correct properties are selected in the %1$s Analytics Settings%2$s.', 'rank-math-pro' ), '<a href="' . Helper::get_admin_url( 'options-general#setting-panel-analytics' ) . '">', '</a>' ); ?></p>
</td>
</tr>
</table>
<?php } ?>

View File

@@ -0,0 +1,69 @@
<?php
/**
* Analytics Report email styling.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
defined( 'ABSPATH' ) || exit;
?>
<style>
.header {
background: ###HEADER_BACKGROUND###;
}
tr.keywords-table-spacer {
height: 14px;
}
table.stats-table tr.table-heading {
background: #243B53;
color: #fff;
font-weight: 500;
}
.stats-table {
overflow: hidden;
}
.stats-table td {
padding: 10px;
}
.stats-table tr:nth-child(2n+1) {
background: #F0F4F8;
}
.stats-table .stat-value {
font-size: 16px;
}
.stats-table .stat-diff, .stats-table .diff-sign {
font-size: 14px;
}
.report-heading {
margin: 40px 0 20px 0;
}
span.post-title, span.post-url {
display: block;
max-width: 250px;
}
.stats-table a {
color: #3f77d6;
font-size: 15px;
font-weight: 600;
}
span.post-url {
color: #92949f;
font-size: 14px;
font-weight: normal;
}
###CUSTOM_CSS###
</style>

View File

@@ -0,0 +1,45 @@
<?php
/**
* Analytics Report email template.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
defined( 'ABSPATH' ) || exit;
$sections = $this->get_setting( 'sections', [ 'summary', 'positions', 'winning_posts', 'winning_keywords', 'losing_keywords' ] );
$analytics = get_option( 'rank_math_google_analytic_options' );
$is_analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );
// Header & optional sections.
$this->template_part( 'header' );
$this->template_part( 'header-after' );
foreach ( $sections as $section ) {
$template = str_replace( '_', '-', $section );
$this->template_part( "sections/{$template}", [ 'analytics_connected' => $is_analytics_connected ] );
}
// phpcs:enable
?>
<?php if ( $this->get_setting( 'link_full_report', true ) ) : ?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="details-button">
<tr class="button">
<td align="left">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left" style="padding-bottom: 0;">
<a href="###REPORT_URL###" target="_blank"><?php esc_html_e( 'VIEW DETAILED ANALYTICS', 'rank-math-pro' ); ?></a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<?php endif; ?>
<?php $this->template_part( 'footer' ); ?>

View File

@@ -0,0 +1,65 @@
<?php
/**
* Analytics Report Losing Keywords.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$keywords = (array) $this->get_variable( 'losing_keywords' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Losing Keywords', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table-wrapper">
<tr>
<td>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table stats-table losing-keywords">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Keywords', 'rank-math-pro' ); ?>
</td>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $keywords as $keyword => $data ) : ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td style="width:280px;box-sizing:border-box;">
<span title="<?php echo esc_html( $keyword ); ?>"><?php echo esc_html( Str::truncate( $keyword, 36, '...' ) ); ?></span>
</td>
<td>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $keywords ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>
</td>
</tr>
</table>

View File

@@ -0,0 +1,73 @@
<?php
/**
* Analytics Report Losing Posts.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$posts = (array) $this->get_variable( 'losing_posts' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Losing Posts', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="traffic-table stats-table">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Post', 'rank-math-pro' ); ?>
</td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<td class="col-2">
<?php esc_html_e( 'Search Traffic', 'rank-math-pro' ); ?>
</td>
<?php else : ?>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<?php endif; ?>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $posts as $post_url => $data ) : // phpcs:disable ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td>
<a href="###SITE_URL###<?php echo esc_attr( $post_url ); ?>" target="_blank">
<span class="post-title"><?php echo esc_html( Str::truncate( ( ! empty( $data['title'] ) ? $data['title'] : $data['page'] ), 55, '...' ) ); ?></span>
<span class="post-url"><?php echo esc_html( Email_Reports::shorten_url( $post_url, 30, '...' ) ); ?></span>
</a>
</td>
<td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'pageviews' ) ); ?>
<?php else : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
<?php endif; ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $posts ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>

View File

@@ -0,0 +1,117 @@
<?php
/**
* Analytics Report summary table template.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
defined( 'ABSPATH' ) || exit;
$analytics = get_option( 'rank_math_google_analytic_options' );
$is_analytics_connected = ! empty( $analytics ) && ! empty( $analytics['view_id'] );
?>
<?php if ( $this->get_variable( 'stats_invalid_data' ) ) { ?>
<?php return; ?>
<?php } ?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="stats">
<tr>
<?php if ( $is_analytics_connected ) : ?>
<td class="col-1">
<h3><?php esc_html_e( 'Search Traffic', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_traffic' ),
'diff' => $this->get_variable( 'stats_traffic_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'traffic' ),
]
);
?>
</td>
<?php else : ?>
<td class="col-1">
<h3><?php esc_html_e( 'Total Impressions', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_impressions' ),
'diff' => $this->get_variable( 'stats_impressions_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'impressions' ),
]
);
?>
</td>
<?php endif; ?>
<?php if ( ! $is_analytics_connected ) : ?>
<td class="col-2">
<h3><?php esc_html_e( 'Total Clicks', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_clicks' ),
'diff' => $this->get_variable( 'stats_clicks_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'clicks' ),
]
);
?>
</td>
<?php else : ?>
<td class="col-2">
<h3><?php esc_html_e( 'Total Impressions', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_impressions' ),
'diff' => $this->get_variable( 'stats_impressions_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'impressions' ),
]
);
?>
</td>
<?php endif; ?>
</tr>
<tr>
<td class="col-1">
<h3><?php esc_html_e( 'Total Keywords', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_keywords' ),
'diff' => $this->get_variable( 'stats_keywords_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'keywords' ),
]
);
?>
</td>
<td class="col-2">
<h3><?php esc_html_e( 'Average Position', 'rank-math-pro' ); ?></h3>
<?php
$this->template_part(
'stat',
[
'value' => $this->get_variable( 'stats_position' ),
'diff' => $this->get_variable( 'stats_position_diff' ),
'graph' => true,
'graph_data' => $this->get_graph_data( 'position' ),
'graph_modifier' => -100,
'human_number' => false,
]
);
?>
</td>
</tr>
</table>

View File

@@ -0,0 +1,65 @@
<?php
/**
* Analytics Report Winning Keywords.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$keywords = (array) $this->get_variable( 'winning_keywords' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Winning Keywords', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table-wrapper">
<tr>
<td>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="keywords-table stats-table winning-keywords">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Keywords', 'rank-math-pro' ); ?>
</td>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $keywords as $keyword => $data ) : ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td style="width:280px;box-sizing:border-box;">
<span title="<?php echo esc_html( $keyword ); ?>"><?php echo esc_html( Str::truncate( $keyword, 36, '...' ) ); ?></span>
</td>
<td>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $keywords ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>
</td>
</tr>
</table>

View File

@@ -0,0 +1,73 @@
<?php
/**
* Analytics Report Winning Posts.
*
* @package RankMath
* @subpackage RankMath\Admin
*/
use RankMathPro\Analytics\Email_Reports;
use MyThemeShop\Helpers\Str;
defined( 'ABSPATH' ) || exit;
$posts = (array) $this->get_variable( 'winning_posts' );
?>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="report-heading">
<tr>
<td>
<h2><?php esc_html_e( 'Top Winning Posts', 'rank-math-pro' ); ?></h2>
</td>
</tr>
</table>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="traffic-table stats-table">
<tr class="table-heading">
<td class="col-1">
<?php esc_html_e( 'Post', 'rank-math-pro' ); ?>
</td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<td class="col-2">
<?php esc_html_e( 'Search Traffic', 'rank-math-pro' ); ?>
</td>
<?php else : ?>
<td class="col-2">
<?php esc_html_e( 'Impressions', 'rank-math-pro' ); ?>
</td>
<?php endif; ?>
<td class="col-3">
<?php esc_html_e( 'Position', 'rank-math-pro' ); ?>
</td>
</tr>
<?php foreach ( $posts as $post_url => $data ) : // phpcs:disable ?>
<?php if ( ! is_array( $data ) ) { continue; } ?>
<tr>
<td>
<a href="###SITE_URL###<?php echo esc_attr( $post_url ); ?>" target="_blank">
<span class="post-title"><?php echo esc_html( Str::truncate( ( ! empty( $data['title'] ) ? $data['title'] : $data['page'] ), 55, '...' ) ); ?></span>
<span class="post-url"><?php echo esc_html( Email_Reports::shorten_url( $post_url, 30, '...' ) ); ?></span>
</a>
</td>
<td>
<?php if ( ! empty( $analytics_connected ) ) : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'pageviews' ) ); ?>
<?php else : ?>
<?php $this->template_part( 'stat', Email_Reports::get_stats_val( $data, 'impressions' ) ); ?>
<?php endif; ?>
</td>
<td>
<?php $this->template_part( 'stat', array_merge( Email_Reports::get_stats_val( $data, 'position' ), [ 'invert' => true ] ) ); ?>
</td>
</tr>
<?php endforeach; ?>
<?php if ( empty( $posts ) ) : ?>
<tr>
<td colspan="3">
<?php esc_html_e( 'No data to show.', 'rank-math-pro' ); ?>
</td>
</tr>
<?php endif; ?>
</table>

View File

@@ -0,0 +1,93 @@
<?php
/**
* Google Adsense.
*
* @since 1.0.49
* @package RankMathPro
* @subpackage RankMathPro\Adsense
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use Exception;
use MyThemeShop\Helpers\DB;
use RankMath\Analytics\Workflow\Base;
use function as_unschedule_all_actions;
defined( 'ABSPATH' ) || exit;
/**
* Adsense class.
*/
class Adsense extends Base {
/**
* Constructor.
*/
public function __construct() {
// If adsense is not connected, no need to proceed.
if ( ! \RankMathPro\Google\Adsense::is_adsense_connected() ) {
return;
}
$this->action( 'rank_math/analytics/workflow/adsense', 'kill_jobs', 5, 0 );
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables' );
$this->action( 'rank_math/analytics/workflow/adsense', 'create_tables', 6, 0 );
$this->action( 'rank_math/analytics/workflow/adsense', 'create_data_jobs', 10, 3 );
}
/**
* Kill jobs.
*
* Stop processing queue items, clear cronjob and delete all batches.
*/
public function kill_jobs() {
as_unschedule_all_actions( 'rank_math/analytics/get_adsense_data' );
}
/**
* Create tables.
*/
public function create_tables() {
global $wpdb;
$collate = $wpdb->get_charset_collate();
$table = 'rank_math_analytics_adsense';
// Early Bail!!
if ( DB::check_table_exists( $table ) ) {
return;
}
$schema = "CREATE TABLE {$wpdb->prefix}{$table} (
id bigint(20) unsigned NOT NULL auto_increment,
created timestamp NOT NULL,
earnings double NOT NULL default 0,
PRIMARY KEY (id)
) $collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
try {
dbDelta( $schema );
} catch ( Exception $e ) { // phpcs:ignore
// Will log.
}
}
/**
* Create jobs to fetch data.
*
* @param integer $days Number of days to fetch from past.
* @param string $prev Previous saved value.
* @param string $new New posted value.
*/
public function create_data_jobs( $days, $prev, $new ) {
// If saved and new profile are same.
if ( ! $this->is_profile_updated( 'adsense_id', $prev, $new ) ) {
return;
}
$this->schedule_single_action( $days, 'adsense' );
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* Google Analytics.
*
* @since 1.0.49
* @package RankMathPro
* @subpackage RankMathPro\Analytics
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use Exception;
use MyThemeShop\Helpers\DB;
use RankMath\Analytics\Workflow\Base;
use function as_unschedule_all_actions;
defined( 'ABSPATH' ) || exit;
/**
* Analytics class.
*/
class Analytics extends Base {
/**
* Constructor.
*/
public function __construct() {
// If analytics is not connected, no need to proceed.
if ( ! \RankMath\Google\Analytics::is_analytics_connected() ) {
return;
}
$this->action( 'rank_math/analytics/workflow/analytics', 'kill_jobs', 5, 0 );
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables' );
$this->action( 'rank_math/analytics/workflow/analytics', 'create_tables', 6, 0 );
$this->action( 'rank_math/analytics/workflow/analytics', 'create_data_jobs', 10, 3 );
}
/**
* Kill jobs.
*
* Stop processing queue items, clear cronjob and delete all batches.
*/
public function kill_jobs() {
as_unschedule_all_actions( 'rank_math/analytics/get_analytics_data' );
}
/**
* Create tables.
*/
public function create_tables() {
global $wpdb;
$collate = $wpdb->get_charset_collate();
$table = 'rank_math_analytics_ga';
// Early Bail!!
if ( DB::check_table_exists( $table ) ) {
return;
}
$schema = "CREATE TABLE {$wpdb->prefix}{$table} (
id bigint(20) unsigned NOT NULL auto_increment,
page varchar(500) NOT NULL,
created timestamp NOT NULL,
pageviews mediumint(6) NOT NULL,
visitors mediumint(6) NOT NULL,
PRIMARY KEY (id),
KEY analytics_object_analytics (page(190))
) $collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
try {
dbDelta( $schema );
} catch ( Exception $e ) { // phpcs:ignore
// Will log.
}
}
/**
* Create jobs to fetch data.
*
* @param integer $days Number of days to fetch from past.
* @param string $prev Previous saved value.
* @param string $new New posted value.
*/
public function create_data_jobs( $days, $prev, $new ) {
// If saved and new profile are same.
if ( ! $this->is_profile_updated( 'view_id', $prev, $new ) ) {
return;
}
// Fetch now.
$this->schedule_single_action( $days, 'analytics' );
}
}

View File

@@ -0,0 +1,325 @@
<?php
/**
* Jobs.
*
* @since 1.0.54
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use DateTime;
use Exception;
use RankMath\Helper;
use RankMath\Traits\Hooker;
use RankMathPro\Analytics\DB;
use RankMath\Analytics\Workflow\Base;
use RankMath\Analytics\DB as AnalyticsDB;
use RankMathPro\Google\Adsense;
use RankMath\Google\Analytics;
use RankMath\Analytics\Workflow\Jobs as AnalyticsJobs;
defined( 'ABSPATH' ) || exit;
/**
* Jobs class.
*/
class Jobs {
use Hooker;
/**
* Is an Analytics account connected?
*
* @var boolean
*/
private $analytics_connected = false;
/**
* Is an AdSense account connected?
*
* @var boolean
*/
private $adsense_connected = false;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Jobs
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Jobs ) ) {
$instance = new Jobs();
$instance->hooks();
}
return $instance;
}
/**
* Hooks.
*/
public function hooks() {
$this->analytics_connected = Analytics::is_analytics_connected();
$this->adsense_connected = \RankMathPro\Google\Adsense::is_adsense_connected();
// Check missing data for analytics and adsense.
$this->action( 'rank_math/analytics/data_fetch', 'data_fetch' );
// Data Fetcher.
if ( $this->adsense_connected ) {
$this->filter( 'rank_math/analytics/get_adsense_days', 'get_adsense_days' );
$this->action( 'rank_math/analytics/get_adsense_data', 'get_adsense_data', 10, 2 );
}
if ( $this->analytics_connected ) {
$this->action( 'rank_math/analytics/get_analytics_days', 'get_analytics_days' );
$this->action( 'rank_math/analytics/get_analytics_data', 'get_analytics_data' );
$this->action( 'rank_math/analytics/handle_analytics_response', 'handle_analytics_response' );
$this->action( 'rank_math/analytics/clear_cache', 'clear_cache' );
}
// Cache.
$this->action( 'rank_math/analytics/purge_cache', 'purge_cache' );
$this->action( 'rank_math/analytics/delete_by_days', 'delete_by_days' );
$this->action( 'rank_math/analytics/delete_data_log', 'delete_data_log' );
}
/**
* Check missing data for analytics and adsense. Perform this task periodically.
*/
public function data_fetch() {
if ( $this->analytics_connected ) {
AnalyticsJobs::get()->check_for_missing_dates( 'analytics' );
}
if ( $this->adsense_connected ) {
AnalyticsJobs::get()->check_for_missing_dates( 'adsense' );
}
}
/**
* Set the analytics start and end dates.
*/
public function get_analytics_days( $args = [] ) {
$rows = Analytics::get_analytics(
[
'start_date' => $args['start_date'],
'end_date' => $args['end_date'],
],
true
);
if ( is_wp_error( $rows ) || empty( $rows ) ) {
return [];
}
$empty_dates = get_option( 'rank_math_analytics_empty_dates', [] );
$dates = [];
foreach ( $rows as $row ) {
$date = '';
// GA4
if ( isset( $row['dimensionValues'] ) ) {
$date = $row['dimensionValues'][0]['value'];
} elseif ( isset( $row['dimensions'] ) ) {
$date = $row['dimensions'][0];
}
if ( ! empty( $date ) ) {
$date = substr( $date, 0, 4 ) . '-' . substr( $date, 4, 2 ) . '-' . substr( $date, 6, 2 );
if ( ! AnalyticsDB::date_exists( $date, 'analytics' ) && ! in_array( $date, $empty_dates, true ) ) {
$dates[] = [
'start_date' => $date,
'end_date' => $date,
];
}
}
}
return $dates;
}
/**
* Get analytics data and save it into database.
*
* @param string $date Date to fetch data for.
*/
public function get_analytics_data( $date ) {
$rows = Analytics::get_analytics(
[
'start_date' => $date,
'end_date' => $date,
]
);
if ( is_wp_error( $rows ) || empty( $rows ) ) {
return [];
}
try {
DB::add_analytics_bulk( $date, $rows );
return $rows;
} catch ( Exception $e ) {} // phpcs:ignore
}
/**
* Set the AdSense start and end dates.
*/
public function get_adsense_days( $args = [] ) {
$dates = [];
$begin = new DateTime( $args['start_date'] );
$end = new DateTime( $args['end_date'] );
$missing_dates = [];
for ( $i = $end; $i >= $begin; $i->modify( '-1 day' ) ) {
$date = $i->format( 'Y-m-d' );
if ( ! AnalyticsDB::date_exists( $date, 'adsense' ) ) {
$missing_dates[] = $date;
}
}
if ( empty( $missing_dates ) ) {
$dates[] = [
'start_date' => $args['start_date'],
'end_date' => $args['end_date'],
];
return $dates;
}
// Request for one date range because its not large data to send individual request for each date.
$dates[] = [
'start_date' => $missing_dates[ count( $missing_dates ) - 1 ],
'end_date' => $missing_dates[0],
];
return $dates;
}
/**
* Get adsense data and save it into database.
*
* @param string $start_date The start date to fetch.
* @param string $end_date The end date to fetch.
*/
public function get_adsense_data( $start_date = '', $end_date = '' ) {
$rows = Adsense::get_adsense(
[
'start_date' => $start_date,
'end_date' => $end_date,
]
);
if ( is_wp_error( $rows ) || empty( $rows ) ) {
return [];
}
try {
DB::add_adsense( $rows );
return $rows;
} catch ( Exception $e ) {} // phpcs:ignore
}
/**
* Handlle analytics response.
*
* @param array $data API request and response data.
*/
public function handle_analytics_response( $data = [] ) {
if ( 200 !== $data['code'] ) {
return;
}
if ( isset( $data['formatted_response']['rows'] ) && ! empty( $data['formatted_response']['rows'] ) ) {
return;
}
$dates = get_option( 'rank_math_analytics_empty_dates', [] );
if ( ! $dates ) {
$dates = [];
}
$dates[] = $data['args']['dateRanges'][0]['startDate'];
$dates[] = $data['args']['dateRanges'][0]['endDate'];
$dates = array_unique( $dates );
update_option( 'rank_math_analytics_empty_dates', $dates );
}
/**
* Clear cache.
*/
public function clear_cache() {
global $wpdb;
// Delete all useless data from analytics data table.
$wpdb->get_results( "DELETE FROM {$wpdb->prefix}rank_math_analytics_ga WHERE page NOT IN ( SELECT page from {$wpdb->prefix}rank_math_analytics_objects )" );
}
/**
* Purge cache.
*
* @param object $table Table insance.
*/
public function purge_cache( $table ) {
$table->whereLike( 'option_name', 'losing_posts' )->delete();
$table->whereLike( 'option_name', 'winning_posts' )->delete();
$table->whereLike( 'option_name', 'losing_keywords' )->delete();
$table->whereLike( 'option_name', 'winning_keywords' )->delete();
$table->whereLike( 'option_name', 'tracked_keywords_summary' )->delete();
}
/**
* Delete analytics and adsense data by days.
*
* @param int $days Decide whether to delete all or delete 90 days data.
*/
public function delete_by_days( $days ) {
if ( -1 === $days ) {
if ( $this->analytics_connected ) {
DB::traffic()->truncate();
}
if ( $this->adsense_connected ) {
DB::adsense()->truncate();
}
return;
}
$start = date_i18n( 'Y-m-d H:i:s', strtotime( '-1 days' ) );
$end = date_i18n( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days' ) );
if ( $this->analytics_connected ) {
DB::traffic()->whereBetween( 'created', [ $end, $start ] )->delete();
}
if ( $this->adsense_connected ) {
DB::adsense()->whereBetween( 'created', [ $end, $start ] )->delete();
}
}
/**
* Delete record for comparison.
*
* @param string $start Start date.
*/
public function delete_data_log( $start ) {
if ( $this->analytics_connected ) {
DB::traffic()->where( 'created', '<', $start )->delete();
}
if ( $this->adsense_connected ) {
DB::adsense()->where( 'created', '<', $start )->delete();
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* Install Keyword manager.
*
* @since 1.0.49
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use Exception;
use MyThemeShop\Helpers\DB;
use RankMath\Analytics\Workflow\Base;
defined( 'ABSPATH' ) || exit;
/**
* Keywords class.
*/
class Keywords extends Base {
/**
* Constructor.
*/
public function __construct() {
$done = \boolval( get_option( 'rank_math_analytics_pro_installed' ) );
if ( $done ) {
return;
}
$this->create_keywords_tables();
update_option( 'rank_math_analytics_pro_installed', true );
}
/**
* Create keywords tables.
*/
public function create_keywords_tables() {
global $wpdb;
$collate = $wpdb->get_charset_collate();
$table = 'rank_math_analytics_keyword_manager';
// Early Bail!!
if ( DB::check_table_exists( $table ) ) {
return;
}
$schema = "CREATE TABLE {$wpdb->prefix}{$table} (
id bigint(20) unsigned NOT NULL auto_increment,
keyword varchar(1000) NOT NULL,
collection varchar(200) NULL,
is_active tinyint(1) NOT NULL default 1,
PRIMARY KEY (id)
) $collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
try {
dbDelta( $schema );
} catch ( Exception $e ) { // phpcs:ignore
// Will log.
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* Workflow.
*
* @since 1.0.54
* @package RankMathPro
* @subpackage RankMathPro\modules
* @author Rank Math <support@rankmath.com>
*/
namespace RankMathPro\Analytics\Workflow;
use RankMath\Traits\Hooker;
use function as_enqueue_async_action;
use function as_unschedule_all_actions;
defined( 'ABSPATH' ) || exit;
/**
* Workflow class.
*/
class Workflow {
use Hooker;
/**
* Main instance
*
* Ensure only one instance is loaded or can be loaded.
*
* @return Workflow
*/
public static function get() {
static $instance;
if ( is_null( $instance ) && ! ( $instance instanceof Workflow ) ) {
$instance = new Workflow();
$instance->hooks();
}
return $instance;
}
/**
* Hooks.
*/
public function hooks() {
// Common.
$this->action( 'rank_math/analytics/workflow', 'maybe_first_install', 5, 0 );
$this->action( 'rank_math/analytics/workflow/create_tables', 'create_tables_only', 5 );
// Services.
$this->action( 'rank_math/analytics/workflow/analytics', 'init_analytics_workflow', 5, 0 );
$this->action( 'rank_math/analytics/workflow/adsense', 'init_adsense_workflow', 5, 0 );
}
/**
* Maybe first install.
*/
public function maybe_first_install() {
new \RankMathPro\Analytics\Workflow\Keywords();
}
/**
* Init Analytics workflow
*/
public function init_analytics_workflow() {
new \RankMathPro\Analytics\Workflow\Analytics();
}
/**
* Init Adsense workflow
*/
public function init_adsense_workflow() {
new \RankMathPro\Analytics\Workflow\Adsense();
}
/**
* Create tables only.
*/
public function create_tables_only() {
new \RankMathPro\Analytics\Workflow\Analytics();
new \RankMathPro\Analytics\Workflow\Adsense();
( new \RankMathPro\Analytics\Workflow\Keywords() )->create_keywords_tables();
}
}