Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Analytics.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Google;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use WP_Error;
|
||||
use RankMath\Google\Api;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Analytics\Workflow\Base;
|
||||
|
||||
/**
|
||||
* Analytics class.
|
||||
*/
|
||||
class Analytics extends Request {
|
||||
|
||||
/**
|
||||
* Get analytics accounts.
|
||||
*/
|
||||
public function get_analytics_accounts() {
|
||||
$accounts = [];
|
||||
$v3_response = $this->http_get( 'https://www.googleapis.com/analytics/v3/management/accountSummaries' );
|
||||
$v3_data = true;
|
||||
if ( ! $this->is_success() || isset( $v3_response->error ) ) {
|
||||
$v3_data = false;
|
||||
}
|
||||
if ( false !== $v3_data ) {
|
||||
foreach ( $v3_response['items'] as $account ) {
|
||||
if ( 'analytics#accountSummary' !== $account['kind'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$properties = [];
|
||||
$account_id = $account['id'];
|
||||
|
||||
foreach ( $account['webProperties'] as $property ) {
|
||||
$property_id = $property['id'];
|
||||
|
||||
$properties[ $property_id ] = [
|
||||
'name' => $property['name'],
|
||||
'id' => $property['id'],
|
||||
'url' => $property['websiteUrl'],
|
||||
'account_id' => $account_id,
|
||||
];
|
||||
|
||||
foreach ( $property['profiles'] as $profile ) {
|
||||
unset( $profile['kind'] );
|
||||
$properties[ $property_id ]['profiles'][ $profile['id'] ] = $profile;
|
||||
}
|
||||
}
|
||||
|
||||
$accounts[ $account_id ] = [
|
||||
'name' => $account['name'],
|
||||
'properties' => $properties,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->add_ga4_accounts( $accounts );
|
||||
}
|
||||
/**
|
||||
* Get GA4 accounts info.
|
||||
*
|
||||
* @param array $accounts GA3 accounts info or empty array.
|
||||
*
|
||||
* @return array $accounts with added ga4 accounts
|
||||
*/
|
||||
public function add_ga4_accounts( $accounts ) {
|
||||
|
||||
$v4_response = $this->http_get( 'https://analyticsadmin.googleapis.com/v1alpha/accountSummaries?pageSize=200' );
|
||||
if ( ! $this->is_success() || isset( $v4_response->error ) ) {
|
||||
return $accounts;
|
||||
}
|
||||
foreach ( $v4_response['accountSummaries'] as $account ) {
|
||||
if ( empty( $account['propertySummaries'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$properties = [];
|
||||
$account_id = str_replace( 'accounts/', '', $account['account'] );
|
||||
|
||||
foreach ( $account['propertySummaries'] as $property ) {
|
||||
$property_id = str_replace( 'properties/', '', $property['property'] );
|
||||
|
||||
$accounts[ $account_id ]['properties'][ $property_id ] = [
|
||||
'name' => $property['displayName'],
|
||||
'id' => $property_id,
|
||||
'account_id' => $account_id,
|
||||
'type' => 'GA4',
|
||||
];
|
||||
}
|
||||
}
|
||||
return $accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if google analytics is connected.
|
||||
*
|
||||
* @return boolean Returns True if the google analytics is connected, otherwise False.
|
||||
*/
|
||||
public static function is_analytics_connected() {
|
||||
$account = wp_parse_args(
|
||||
get_option( 'rank_math_google_analytic_options' ),
|
||||
[ 'view_id' => '' ]
|
||||
);
|
||||
|
||||
return ! empty( $account['view_id'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Query analytics data from google client api.
|
||||
*
|
||||
* @param array $options Analytics options.
|
||||
* @param boolean $days Whether to include dates.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_analytics( $options = [], $days = false ) {
|
||||
// Check view ID.
|
||||
$view_id = isset( $options['view_id'] ) ? $options['view_id'] : self::get_view_id();
|
||||
if ( ! $view_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stored = get_option(
|
||||
'rank_math_google_analytic_options',
|
||||
[
|
||||
'account_id' => '',
|
||||
'property_id' => '',
|
||||
'view_id' => '',
|
||||
'measurement_id' => '',
|
||||
'stream_name' => '',
|
||||
'country' => '',
|
||||
'install_code' => '',
|
||||
'anonymize_ip' => '',
|
||||
'local_ga_js' => '',
|
||||
'exclude_loggedin' => '',
|
||||
]
|
||||
);
|
||||
|
||||
// Check property ID.
|
||||
$property_id = isset( $options['property_id'] ) ? $options['property_id'] : $stored['property_id'];
|
||||
if ( ! $property_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check dates.
|
||||
$dates = Base::get_dates();
|
||||
$start_date = isset( $options['start_date'] ) ? $options['start_date'] : $dates['start_date'];
|
||||
$end_date = isset( $options['end_date'] ) ? $options['end_date'] : $dates['end_date'];
|
||||
if ( ! $start_date || ! $end_date ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request params.
|
||||
$row_limit = isset( $options['row_limit'] ) ? $options['row_limit'] : Api::get()->get_row_limit();
|
||||
$country = isset( $options['country'] ) ? $options['country'] : '';
|
||||
if ( ! empty( $stored['country'] ) && 'all' !== $stored['country'] ) {
|
||||
$country = $stored['country'];
|
||||
}
|
||||
|
||||
// Check the property for old Google Analytics.
|
||||
if ( Str::starts_with( 'UA-', $property_id ) ) {
|
||||
$args = [
|
||||
'viewId' => $view_id,
|
||||
'pageSize' => $row_limit,
|
||||
'dateRanges' => [
|
||||
[
|
||||
'startDate' => $start_date,
|
||||
'endDate' => $end_date,
|
||||
],
|
||||
],
|
||||
'dimensionFilterClauses' => [
|
||||
[
|
||||
'filters' => [
|
||||
[
|
||||
'dimensionName' => 'ga:medium',
|
||||
'operator' => 'EXACT',
|
||||
'expressions' => 'organic',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Include only dates.
|
||||
if ( true === $days ) {
|
||||
$args = wp_parse_args(
|
||||
[
|
||||
'dimensions' => [
|
||||
[ 'name' => 'ga:date' ],
|
||||
],
|
||||
],
|
||||
$args
|
||||
);
|
||||
} else {
|
||||
$args = wp_parse_args(
|
||||
[
|
||||
'metrics' => [
|
||||
[ 'expression' => 'ga:pageviews' ],
|
||||
[ 'expression' => 'ga:users' ],
|
||||
],
|
||||
'dimensions' => [
|
||||
[ 'name' => 'ga:date' ],
|
||||
[ 'name' => 'ga:pagePath' ],
|
||||
[ 'name' => 'ga:hostname' ],
|
||||
],
|
||||
'orderBys' => [
|
||||
[
|
||||
'fieldName' => 'ga:pageviews',
|
||||
'sortOrder' => 'DESCENDING',
|
||||
],
|
||||
],
|
||||
],
|
||||
$args
|
||||
);
|
||||
|
||||
// Add country.
|
||||
if ( ! $country ) {
|
||||
$args['dimensionFilterClauses'][0]['filters'][] = [
|
||||
'dimensionName' => 'ga:countryIsoCode',
|
||||
'operator' => 'EXACT',
|
||||
'expressions' => $country,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response = Api::get()->http_post(
|
||||
'https://analyticsreporting.googleapis.com/v4/reports:batchGet',
|
||||
[
|
||||
'reportRequests' => [ $args ],
|
||||
]
|
||||
);
|
||||
|
||||
Api::get()->log_failed_request( $response, 'analytics', $start_date, func_get_args() );
|
||||
|
||||
if ( ! Api::get()->is_success() ) {
|
||||
return new WP_Error( 'request_failed', __( 'The Google Analytics request failed.', 'rank-math' ) );
|
||||
}
|
||||
|
||||
if ( ! isset( $response['reports'], $response['reports'][0]['data']['rows'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response['reports'][0]['data']['rows'];
|
||||
}
|
||||
|
||||
// Request for GA4 API.
|
||||
$args = [
|
||||
'dateRanges' => [
|
||||
[
|
||||
'startDate' => $start_date,
|
||||
'endDate' => $end_date,
|
||||
],
|
||||
],
|
||||
'dimensionFilter' => [
|
||||
'andGroup' => [
|
||||
'expressions' => [
|
||||
[
|
||||
'filter' => [
|
||||
'fieldName' => 'streamId',
|
||||
'stringFilter' => [
|
||||
'matchType' => 'EXACT',
|
||||
'value' => $view_id,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'filter' => [
|
||||
'fieldName' => 'sessionMedium',
|
||||
'stringFilter' => [
|
||||
'matchType' => 'EXACT',
|
||||
'value' => 'organic',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Include only dates.
|
||||
if ( true === $days ) {
|
||||
$args = wp_parse_args(
|
||||
[
|
||||
'dimensions' => [
|
||||
[ 'name' => 'date' ],
|
||||
],
|
||||
],
|
||||
$args
|
||||
);
|
||||
} else {
|
||||
$args = wp_parse_args(
|
||||
[
|
||||
'dimensions' => [
|
||||
[ 'name' => 'hostname' ],
|
||||
[ 'name' => 'pagePath' ],
|
||||
[ 'name' => 'countryId' ],
|
||||
[ 'name' => 'sessionMedium' ],
|
||||
],
|
||||
'metrics' => [
|
||||
[ 'name' => 'screenPageViews' ],
|
||||
[ 'name' => 'totalUsers' ],
|
||||
],
|
||||
],
|
||||
$args
|
||||
);
|
||||
|
||||
// Include country.
|
||||
if ( $country ) {
|
||||
$args['dimensionFilter']['andGroup']['expressions'][] = [
|
||||
'filter' => [
|
||||
'fieldName' => 'countryId',
|
||||
'stringFilter' => [
|
||||
'matchType' => 'EXACT',
|
||||
'value' => $country,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$workflow = 'analytics';
|
||||
Api::get()->set_workflow( $workflow );
|
||||
$response = Api::get()->http_post(
|
||||
'https://analyticsdata.googleapis.com/v1beta/properties/' . $property_id . ':runReport',
|
||||
$args
|
||||
);
|
||||
|
||||
Api::get()->log_failed_request( $response, $workflow, $start_date, func_get_args() );
|
||||
|
||||
if ( ! Api::get()->is_success() ) {
|
||||
return new WP_Error( 'request_failed', __( 'The Google Analytics Console request failed.', 'rank-math' ) );
|
||||
}
|
||||
|
||||
if ( ! isset( $response['rows'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response['rows'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_view_id() {
|
||||
static $rank_math_view_id;
|
||||
|
||||
if ( is_null( $rank_math_view_id ) ) {
|
||||
$options = get_option( 'rank_math_google_analytic_options' );
|
||||
$rank_math_view_id = ! empty( $options['view_id'] ) ? $options['view_id'] : false;
|
||||
}
|
||||
|
||||
return $rank_math_view_id;
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Minimal Google API wrapper.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Google;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Api
|
||||
*/
|
||||
class Api extends Console {
|
||||
|
||||
/**
|
||||
* Access token.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $token = [];
|
||||
|
||||
/**
|
||||
* Main instance
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Api
|
||||
*/
|
||||
public static function get() {
|
||||
static $instance;
|
||||
|
||||
if ( is_null( $instance ) && ! ( $instance instanceof Api ) ) {
|
||||
$instance = new Api();
|
||||
$instance->setup();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup token.
|
||||
*/
|
||||
private function setup() {
|
||||
if ( ! Authentication::is_authorized() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens = Authentication::tokens();
|
||||
$this->token = $tokens['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row limit.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_row_limit() {
|
||||
return apply_filters( 'rank_math/analytics/row_limit', 10000 );
|
||||
}
|
||||
}
|
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Authentication wrapper.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Google;
|
||||
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Data_Encryption;
|
||||
use RankMath\Helpers\Param;
|
||||
use RankMath\Helpers\Security;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Authentication class.
|
||||
*/
|
||||
class Authentication {
|
||||
|
||||
/**
|
||||
* API version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $api_version = '2.1';
|
||||
|
||||
/**
|
||||
* Get or update token data.
|
||||
*
|
||||
* @param bool|array $data Data to save.
|
||||
* @return bool|array
|
||||
*/
|
||||
public static function tokens( $data = null ) {
|
||||
$key = 'rank_math_google_oauth_tokens';
|
||||
$encrypt_keys = [
|
||||
'access_token',
|
||||
'refresh_token',
|
||||
];
|
||||
|
||||
// Clear data.
|
||||
if ( false === $data ) {
|
||||
delete_option( $key );
|
||||
return false;
|
||||
}
|
||||
|
||||
$saved = get_option( $key, [] );
|
||||
foreach ( $encrypt_keys as $enc_key ) {
|
||||
if ( isset( $saved[ $enc_key ] ) ) {
|
||||
$saved[ $enc_key ] = Data_Encryption::deep_decrypt( $saved[ $enc_key ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Getter.
|
||||
if ( is_null( $data ) ) {
|
||||
return wp_parse_args( $saved, [] );
|
||||
}
|
||||
|
||||
// Setter.
|
||||
foreach ( $encrypt_keys as $enc_key ) {
|
||||
if ( isset( $saved[ $enc_key ] ) ) {
|
||||
$saved[ $enc_key ] = Data_Encryption::deep_encrypt( $saved[ $enc_key ] );
|
||||
}
|
||||
if ( isset( $data[ $enc_key ] ) ) {
|
||||
$data[ $enc_key ] = Data_Encryption::deep_encrypt( $data[ $enc_key ] );
|
||||
}
|
||||
}
|
||||
|
||||
$data = wp_parse_args( $data, $saved );
|
||||
update_option( $key, $data );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is google authorized.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_authorized() {
|
||||
$tokens = self::tokens();
|
||||
|
||||
return isset( $tokens['access_token'] ) && isset( $tokens['refresh_token'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token is expired.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_token_expired() {
|
||||
$tokens = self::tokens();
|
||||
|
||||
return $tokens['expire'] && time() > $tokens['expire'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get oauth url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_auth_url() {
|
||||
$page = self::get_page_slug();
|
||||
|
||||
return Security::add_query_arg_raw(
|
||||
[
|
||||
'version' => defined( 'RANK_MATH_PRO_VERSION' ) ? 'pro' : 'free',
|
||||
'api_version' => static::$api_version,
|
||||
'redirect_uri' => rawurlencode( admin_url( 'admin.php?page=' . $page ) ),
|
||||
'security' => wp_create_nonce( 'rank_math_oauth_token' ),
|
||||
],
|
||||
self::get_auth_app_url()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Google custom app.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_auth_app_url() {
|
||||
return apply_filters( 'rank_math/analytics/app_url', 'https://oauth.rankmath.com' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page slug according to request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_page_slug() {
|
||||
$page = Param::get( 'page' );
|
||||
if ( ! empty( $page ) ) {
|
||||
switch ( $page ) {
|
||||
case 'rank-math-wizard':
|
||||
return 'rank-math-wizard&step=analytics';
|
||||
|
||||
case 'rank-math-analytics':
|
||||
return 'rank-math-analytics';
|
||||
|
||||
default:
|
||||
return 'rank-math-options-general#setting-panel-analytics';
|
||||
}
|
||||
}
|
||||
|
||||
$page = wp_get_referer();
|
||||
if ( ! empty( $page ) && Str::contains( 'wizard', $page ) ) {
|
||||
return 'rank-math-wizard&step=analytics';
|
||||
}
|
||||
|
||||
return 'rank-math-options-general#setting-panel-analytics';
|
||||
}
|
||||
}
|
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Search Console.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Google;
|
||||
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Analytics\Workflow\Base;
|
||||
use RankMath\Sitemap\Sitemap;
|
||||
use WP_Error;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Console class.
|
||||
*/
|
||||
class Console extends Analytics {
|
||||
|
||||
/**
|
||||
* Add site.
|
||||
*
|
||||
* @param string $url Site url to add.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function add_site( $url ) {
|
||||
$this->http_put( 'https://www.googleapis.com/webmasters/v3/sites/' . rawurlencode( $url ) );
|
||||
return $this->is_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site verification token.
|
||||
*
|
||||
* @param string $url Site url to add.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function get_site_verification_token( $url ) {
|
||||
$args = [
|
||||
'site' => [
|
||||
'type' => 'SITE',
|
||||
'identifier' => $url,
|
||||
],
|
||||
'verificationMethod' => 'META',
|
||||
];
|
||||
|
||||
$response = $this->http_post( 'https://www.googleapis.com/siteVerification/v1/token', $args );
|
||||
if ( ! $this->is_success() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \RankMath\CMB2::sanitize_webmaster_tags( $response['token'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify site token.
|
||||
*
|
||||
* @param string $url Site url to add.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function verify_site( $url ) {
|
||||
$token = $this->get_site_verification_token( $url );
|
||||
if ( ! $token ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save in transient.
|
||||
set_transient( 'rank_math_google_site_verification', $token, DAY_IN_SECONDS * 2 );
|
||||
|
||||
// Call Google site verification.
|
||||
$args = [
|
||||
'site' => [
|
||||
'type' => 'SITE',
|
||||
'identifier' => $url,
|
||||
],
|
||||
];
|
||||
|
||||
$this->http_post( 'https://www.googleapis.com/siteVerification/v1/webResource?verificationMethod=META', $args );
|
||||
|
||||
// Sync sitemap.
|
||||
as_enqueue_async_action( 'rank_math/analytics/sync_sitemaps', [], 'rank-math' );
|
||||
|
||||
return $this->is_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sites.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sites() {
|
||||
static $rank_math_google_sites;
|
||||
if ( ! \is_null( $rank_math_google_sites ) ) {
|
||||
return $rank_math_google_sites;
|
||||
}
|
||||
|
||||
$rank_math_google_sites = [];
|
||||
$response = $this->http_get( 'https://www.googleapis.com/webmasters/v3/sites' );
|
||||
if ( ! $this->is_success() || empty( $response['siteEntry'] ) ) {
|
||||
return $rank_math_google_sites;
|
||||
}
|
||||
|
||||
foreach ( $response['siteEntry'] as $site ) {
|
||||
$rank_math_google_sites[ $site['siteUrl'] ] = $site['siteUrl'];
|
||||
}
|
||||
|
||||
return $rank_math_google_sites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch sitemaps.
|
||||
*
|
||||
* @param string $url Site to get sitemaps for.
|
||||
* @param boolean $with_index With index data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sitemaps( $url, $with_index = false ) {
|
||||
$with_index = $with_index ? '?sitemapIndex=' . rawurlencode( $url . Sitemap::get_sitemap_index_slug() . '.xml' ) : '';
|
||||
$response = $this->http_get( 'https://www.googleapis.com/webmasters/v3/sites/' . rawurlencode( $url ) . '/sitemaps' . $with_index );
|
||||
|
||||
if ( ! $this->is_success() || empty( $response['sitemap'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $response['sitemap'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit sitemap to search console.
|
||||
*
|
||||
* @param string $url Site to add sitemap for.
|
||||
* @param string $sitemap Sitemap url.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_sitemap( $url, $sitemap ) {
|
||||
return $this->http_put( 'https://www.googleapis.com/webmasters/v3/sites/' . rawurlencode( $url ) . '/sitemaps/' . rawurlencode( $sitemap ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete sitemap from search console.
|
||||
*
|
||||
* @param string $url Site to delete sitemap for.
|
||||
* @param string $sitemap Sitemap url.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function delete_sitemap( $url, $sitemap ) {
|
||||
return $this->http_delete( 'https://www.googleapis.com/webmasters/v3/sites/' . rawurlencode( $url ) . '/sitemaps/' . rawurlencode( $sitemap ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Query analytics data from google client api.
|
||||
*
|
||||
* @param array $args Query arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_search_analytics( $args = [] ) {
|
||||
$dates = Base::get_dates();
|
||||
|
||||
$start_date = isset( $args['start_date'] ) ? $args['start_date'] : $dates['start_date'];
|
||||
$end_date = isset( $args['end_date'] ) ? $args['end_date'] : $dates['end_date'];
|
||||
$dimensions = isset( $args['dimensions'] ) ? $args['dimensions'] : 'date';
|
||||
$row_limit = isset( $args['row_limit'] ) ? $args['row_limit'] : Api::get()->get_row_limit();
|
||||
|
||||
$params = [
|
||||
'startDate' => $start_date,
|
||||
'endDate' => $end_date,
|
||||
'rowLimit' => $row_limit,
|
||||
'dimensions' => \is_array( $dimensions ) ? $dimensions : [ $dimensions ],
|
||||
];
|
||||
|
||||
$stored = get_option(
|
||||
'rank_math_google_analytic_profile',
|
||||
[
|
||||
'country' => '',
|
||||
'profile' => '',
|
||||
'enable_index_status' => '',
|
||||
]
|
||||
);
|
||||
$country = isset( $args['country'] ) ? $args['country'] : $stored['country'];
|
||||
$profile = isset( $args['profile'] ) ? $args['profile'] : $stored['profile'];
|
||||
|
||||
if ( 'all' !== $country ) {
|
||||
$params['dimensionFilterGroups'] = [
|
||||
[
|
||||
'filters' => [
|
||||
[
|
||||
'dimension' => 'country',
|
||||
'operator' => 'equals',
|
||||
'expression' => $country,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ( empty( $profile ) ) {
|
||||
$profile = trailingslashit( strtolower( home_url() ) );
|
||||
}
|
||||
|
||||
$workflow = 'console';
|
||||
$this->set_workflow( $workflow );
|
||||
$response = $this->http_post(
|
||||
'https://www.googleapis.com/webmasters/v3/sites/' . rawurlencode( $profile ) . '/searchAnalytics/query',
|
||||
$params
|
||||
);
|
||||
|
||||
$this->log_failed_request( $response, $workflow, $start_date, func_get_args() );
|
||||
|
||||
if ( ! $this->is_success() ) {
|
||||
return new WP_Error( 'request_failed', __( 'The Google Search Console request failed.', 'rank-math' ) );
|
||||
}
|
||||
|
||||
if ( ! isset( $response['rows'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response['rows'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Is site verified.
|
||||
*
|
||||
* @param string $url Site to verify.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_site_verified( $url ) {
|
||||
$response = $this->http_get( 'https://www.googleapis.com/siteVerification/v1/webResource/' . rawurlencode( $url ) );
|
||||
if ( ! $this->is_success() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset( $response['owners'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync sitemaps with google search console.
|
||||
*/
|
||||
public function sync_sitemaps() {
|
||||
$site_url = self::get_site_url();
|
||||
$data = $this->get_sitemap_to_sync();
|
||||
|
||||
// Submit it.
|
||||
if ( ! $data['sitemaps_in_list'] ) {
|
||||
$this->add_sitemap( $site_url, $data['local_sitemap'] );
|
||||
}
|
||||
|
||||
if ( empty( $data['delete_sitemaps'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete it.
|
||||
foreach ( $data['delete_sitemaps'] as $sitemap ) {
|
||||
$this->delete_sitemap( $site_url, $sitemap );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sitemaps to sync.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_sitemap_to_sync() {
|
||||
$delete_sitemaps = [];
|
||||
$sitemaps_in_list = false;
|
||||
$site_url = self::get_site_url();
|
||||
$sitemaps = $this->get_sitemaps( $site_url );
|
||||
$local_sitemap = trailingslashit( $site_url ) . Sitemap::get_sitemap_index_slug() . '.xml';
|
||||
|
||||
// Early Bail if there are no sitemaps.
|
||||
if ( empty( $sitemaps ) ) {
|
||||
return compact( 'delete_sitemaps', 'sitemaps_in_list', 'local_sitemap' );
|
||||
}
|
||||
|
||||
foreach ( $sitemaps as $sitemap ) {
|
||||
if ( $sitemap['path'] === $local_sitemap ) {
|
||||
$sitemaps_in_list = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$delete_sitemaps[] = $sitemap['path'];
|
||||
}
|
||||
|
||||
return compact( 'delete_sitemaps', 'sitemaps_in_list', 'local_sitemap' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_site_url() {
|
||||
static $rank_math_site_url;
|
||||
|
||||
if ( is_null( $rank_math_site_url ) ) {
|
||||
$default = trailingslashit( strtolower( home_url() ) );
|
||||
$rank_math_site_url = get_option( 'rank_math_google_analytic_profile', [ 'profile' => $default ] );
|
||||
$rank_math_site_url = empty( $rank_math_site_url['profile'] ) ? $default : $rank_math_site_url['profile'];
|
||||
|
||||
if ( Str::contains( 'sc-domain:', $rank_math_site_url ) ) {
|
||||
$rank_math_site_url = str_replace( 'sc-domain:', '', $rank_math_site_url );
|
||||
$rank_math_site_url = ( is_ssl() ? 'https://' : 'http://' ) . $rank_math_site_url;
|
||||
}
|
||||
}
|
||||
|
||||
return $rank_math_site_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if console is connected.
|
||||
*
|
||||
* @return boolean Returns True if the console is connected, otherwise False.
|
||||
*/
|
||||
public static function is_console_connected() {
|
||||
$profile = wp_parse_args(
|
||||
get_option( 'rank_math_google_analytic_profile' ),
|
||||
[
|
||||
'profile' => '',
|
||||
'country' => 'all',
|
||||
]
|
||||
);
|
||||
|
||||
return ! empty( $profile['profile'] );
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* Google Permissions.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Google;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Permissions class.
|
||||
*/
|
||||
class Permissions {
|
||||
|
||||
const OPTION_NAME = 'rank_math_analytics_permissions';
|
||||
|
||||
/**
|
||||
* Permission info.
|
||||
*/
|
||||
public static function fetch() {
|
||||
$tokens = Authentication::tokens();
|
||||
if ( empty( $tokens['access_token'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' . $tokens['access_token'];
|
||||
$response = wp_remote_get( $url );
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $response ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = \json_decode( $response, true );
|
||||
$scopes = $response['scope'];
|
||||
$scopes = explode( ' ', $scopes );
|
||||
$scopes = str_replace( 'https://www.googleapis.com/auth/', '', $scopes );
|
||||
|
||||
update_option( self::OPTION_NAME, $scopes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permissions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get() {
|
||||
return get_option( self::OPTION_NAME, [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* If user give permission or not.
|
||||
*
|
||||
* @param string $permission Permission name.
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has( $permission ) {
|
||||
$permissions = self::get();
|
||||
|
||||
return in_array( $permission, $permissions, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* If user give permission or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_console() {
|
||||
return self::has( 'webmasters' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If user give permission or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_analytics() {
|
||||
return self::has( 'analytics.readonly' ) ||
|
||||
self::has( 'analytics.provision' ) ||
|
||||
self::has( 'analytics.edit' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If user give permission or not.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_adsense() {
|
||||
return self::has( 'adsense.readonly' );
|
||||
}
|
||||
|
||||
/**
|
||||
* If user give permission or not.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_status() {
|
||||
return [
|
||||
esc_html__( 'Search Console', 'rank-math' ) => self::get_status_text( self::has_console() ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Status text
|
||||
*
|
||||
* @param boolean $check Truthness.
|
||||
* @return string
|
||||
*/
|
||||
public static function get_status_text( $check ) {
|
||||
return $check ? esc_html__( 'Given', 'rank-math' ) : esc_html__( 'Not Given', 'rank-math' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print warning
|
||||
*/
|
||||
public static function print_warning() {
|
||||
?>
|
||||
<p class="warning">
|
||||
<strong class="warning">
|
||||
<?php esc_html_e( 'Warning:', 'rank-math' ); ?>
|
||||
</strong>
|
||||
<?php
|
||||
/* translators: %s is the reconnect link. */
|
||||
printf( wp_kses_post( __( 'You have not given the permission to fetch this data. Please <a href="%s">reconnect</a> with all required permissions.', 'rank-math' ) ), wp_nonce_url( admin_url( 'admin.php?reconnect=google' ), 'rank_math_reconnect_google' ) );
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
}
|
@@ -0,0 +1,489 @@
|
||||
<?php
|
||||
/**
|
||||
* Google API Request.
|
||||
*
|
||||
* @since 1.0.49
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Google;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Request
|
||||
*/
|
||||
class Request {
|
||||
|
||||
/**
|
||||
* Workflow.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $workflow = '';
|
||||
|
||||
/**
|
||||
* Was the last request successful.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_success = false;
|
||||
|
||||
/**
|
||||
* Last error.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $last_error = '';
|
||||
|
||||
/**
|
||||
* Last response.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $last_response = [];
|
||||
|
||||
/**
|
||||
* Last response header code.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $last_code = 0;
|
||||
|
||||
/**
|
||||
* Is refresh token notice added.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $is_notice_added = false;
|
||||
|
||||
/**
|
||||
* Access token.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $token = '';
|
||||
|
||||
/**
|
||||
* Set workflow
|
||||
*
|
||||
* @param string $workflow Workflow name.
|
||||
*/
|
||||
public function set_workflow( $workflow = '' ) {
|
||||
$this->workflow = $workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the last request successful?
|
||||
*
|
||||
* @return bool True for success, false for failure
|
||||
*/
|
||||
public function is_success() {
|
||||
return $this->is_success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last error returned by either the network transport, or by the API.
|
||||
* If something didn't work, this should contain the string describing the problem.
|
||||
*
|
||||
* @return array|false describing the error
|
||||
*/
|
||||
public function get_error() {
|
||||
return $this->last_error ? $this->last_error : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array containing the HTTP headers and the body of the API response.
|
||||
*
|
||||
* @return array Assoc array with keys 'headers' and 'body'
|
||||
*/
|
||||
public function get_response() {
|
||||
return $this->last_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP GET request - for retrieving data.
|
||||
*
|
||||
* @param string $url URL to do request.
|
||||
* @param array $args Assoc array of arguments (usually your data).
|
||||
* @param int $timeout Timeout limit for request in seconds.
|
||||
*
|
||||
* @return WP_Error|array|false Assoc array of API response, decoded from JSON.
|
||||
*/
|
||||
public function http_get( $url, $args = [], $timeout = 10 ) {
|
||||
return $this->make_request( 'GET', $url, $args, $timeout );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP POST request - for creating and updating items.
|
||||
*
|
||||
* @param string $url URL to do request.
|
||||
* @param array $args Assoc array of arguments (usually your data).
|
||||
* @param int $timeout Timeout limit for request in seconds.
|
||||
*
|
||||
* @return WP_Error|array|false Assoc array of API response, decoded from JSON.
|
||||
*/
|
||||
public function http_post( $url, $args = [], $timeout = 10 ) {
|
||||
return $this->make_request( 'POST', $url, $args, $timeout );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP PUT request - for creating new items.
|
||||
*
|
||||
* @param string $url URL to do request.
|
||||
* @param array $args Assoc array of arguments (usually your data).
|
||||
* @param int $timeout Timeout limit for request in seconds.
|
||||
*
|
||||
* @return WP_Error|array|false Assoc array of API response, decoded from JSON.
|
||||
*/
|
||||
public function http_put( $url, $args = [], $timeout = 10 ) {
|
||||
return $this->make_request( 'PUT', $url, $args, $timeout );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP DELETE request - for deleting data.
|
||||
*
|
||||
* @param string $url URL to do request.
|
||||
* @param array $args Assoc array of arguments (usually your data).
|
||||
* @param int $timeout Timeout limit for request in seconds.
|
||||
*
|
||||
* @return WP_Error|array|false Assoc array of API response, decoded from JSON.
|
||||
*/
|
||||
public function http_delete( $url, $args = [], $timeout = 10 ) {
|
||||
return $this->make_request( 'DELETE', $url, $args, $timeout );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the underlying HTTP request. Not very exciting.
|
||||
*
|
||||
* @param string $http_verb The HTTP verb to use: get, post, put, patch, delete.
|
||||
* @param string $url URL to do request.
|
||||
* @param array $args Assoc array of parameters to be passed.
|
||||
* @param int $timeout Timeout limit for request in seconds.
|
||||
*
|
||||
* @return array|false Assoc array of decoded result.
|
||||
*/
|
||||
private function make_request( $http_verb, $url, $args = [], $timeout = 10 ) {
|
||||
// Early Bail!!
|
||||
if ( ! Authentication::is_authorized() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->refresh_token() || ! is_scalar( $this->token ) ) {
|
||||
if ( ! $this->is_notice_added ) {
|
||||
$this->is_notice_added = true;
|
||||
$this->is_success = false;
|
||||
$this->last_error = sprintf(
|
||||
/* translators: reconnect link */
|
||||
wp_kses_post( __( 'There is a problem with the Google auth token. Please <a href="%1$s" class="button button-link rank-math-reconnect-google">reconnect your app</a>', 'rank-math' ) ),
|
||||
wp_nonce_url( admin_url( 'admin.php?reconnect=google' ), 'rank_math_reconnect_google' )
|
||||
);
|
||||
$this->log_response( $http_verb, $url, $args, '', '', '', date( 'Y-m-d H:i:s' ) . ': Google auth token has been expired or is invalid' );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$params = [
|
||||
'timeout' => $timeout,
|
||||
'method' => $http_verb,
|
||||
];
|
||||
|
||||
$params['headers'] = [ 'Authorization' => 'Bearer ' . $this->token ];
|
||||
|
||||
if ( 'DELETE' === $http_verb || 'PUT' === $http_verb ) {
|
||||
$params['headers']['Content-Length'] = '0';
|
||||
} elseif ( 'POST' === $http_verb && ! empty( $args ) && is_array( $args ) ) {
|
||||
$json = wp_json_encode( $args );
|
||||
$params['body'] = $json;
|
||||
$params['headers']['Content-Type'] = 'application/json';
|
||||
$params['headers']['Content-Length'] = strlen( $json );
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
sleep( 1 );
|
||||
$response = wp_remote_request( $url, $params );
|
||||
$formatted_response = $this->format_response( $response );
|
||||
$this->determine_success( $response, $formatted_response );
|
||||
|
||||
$this->log_response( $http_verb, $url, $args, $response, $formatted_response, $params );
|
||||
|
||||
// Error handaling.
|
||||
$code = wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 !== $code ) {
|
||||
// Remove workflow actions.
|
||||
if ( $this->workflow ) {
|
||||
as_unschedule_all_actions( 'rank_math/analytics/get_' . $this->workflow . '_data' );
|
||||
}
|
||||
}
|
||||
|
||||
do_action(
|
||||
'rank_math/analytics/handle_' . $this->workflow . '_response',
|
||||
[
|
||||
'formatted_response' => $formatted_response,
|
||||
'response' => $response,
|
||||
'http_verb' => $http_verb,
|
||||
'url' => $url,
|
||||
'args' => $args,
|
||||
'code' => $code,
|
||||
]
|
||||
);
|
||||
|
||||
return $formatted_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the response in analytics_debug.log file.
|
||||
*
|
||||
* @param string $http_verb The HTTP verb to use: get, post, put, patch, delete.
|
||||
* @param string $url URL to do request.
|
||||
* @param array $args Assoc array of parameters to be passed.
|
||||
* @param string $response make_request response.
|
||||
* @param string $formatted_response Formated response.
|
||||
* @param array $params Parameters.
|
||||
* @param string $text Text to append at the end of the response.
|
||||
*/
|
||||
private function log_response( $http_verb = '', $url = '', $args = [], $response = [], $formatted_response = '', $params = [], $text = '' ) {
|
||||
do_action( 'rank_math/analytics/log', $http_verb, $url, $args, $response, $formatted_response, $params );
|
||||
|
||||
if ( ! apply_filters( 'rank_math/analytics/log_response', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uploads = wp_upload_dir();
|
||||
$file = $uploads['basedir'] . '/rank-math/analytics-debug.log';
|
||||
|
||||
$wp_filesystem = Helper::get_filesystem();
|
||||
|
||||
// Create log file if it doesn't exist.
|
||||
$wp_filesystem->touch( $file );
|
||||
|
||||
// Not writable? Bail.
|
||||
if ( ! $wp_filesystem->is_writable( $file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = '********************************' . PHP_EOL;
|
||||
$message .= date( 'Y-m-d h:i:s' ) . PHP_EOL;
|
||||
|
||||
$tokens = Authentication::tokens();
|
||||
if ( ! empty( $tokens ) && is_array( $tokens ) && isset( $tokens['expire'] ) ) {
|
||||
$message .= 'Expiry: ' . date( 'Y-m-d h:i:s', $tokens['expire'] ) . PHP_EOL;
|
||||
$message .= 'Expiry Readable: ' . human_time_diff( $tokens['expire'] ) . PHP_EOL;
|
||||
}
|
||||
|
||||
$message .= $text . PHP_EOL;
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$message .= '<span class="fail">FAIL</span>' . PHP_EOL;
|
||||
$message .= 'WP_Error: ' . $response->get_error_message() . PHP_EOL;
|
||||
} elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
$message .= '<span class="fail">FAIL</span>' . PHP_EOL;
|
||||
} elseif ( isset( $formatted_response['error_description'] ) ) {
|
||||
$message .= '<span class="fail">FAIL</span>' . PHP_EOL;
|
||||
$message .= 'Bad Request' === $formatted_response['error_description'] ?
|
||||
esc_html__( 'Bad request. Please check the code.', 'rank-math' ) : $formatted_response['error_description'];
|
||||
} else {
|
||||
$message .= '<span class="pass">PASS</span>' . PHP_EOL;
|
||||
}
|
||||
$message .= 'REQUEST: ' . $http_verb . ' > ' . $url . PHP_EOL;
|
||||
$message .= 'REQUEST_PARAMETERS: ' . wp_json_encode( $params ) . PHP_EOL;
|
||||
$message .= 'REQUEST_API_ARGUMENTS: ' . wp_json_encode( $args ) . PHP_EOL;
|
||||
$message .= 'RESPONSE_CODE: ' . wp_remote_retrieve_response_code( $response ) . PHP_EOL;
|
||||
$message .= 'RESPONSE_CODE_MESSAGE: ' . wp_remote_retrieve_body( $response ) . PHP_EOL;
|
||||
$message .= 'RESPONSE_FORMATTED: ' . wp_json_encode( $formatted_response ) . PHP_EOL;
|
||||
$message .= 'ORIGINAL_RESPONSE: ' . wp_json_encode( $response ) . PHP_EOL;
|
||||
$message .= '================================' . PHP_EOL;
|
||||
$message .= $wp_filesystem->get_contents( $file );
|
||||
|
||||
$wp_filesystem->put_contents( $file, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the response and format any error messages for debugging
|
||||
*
|
||||
* @param array $response The response from the curl request.
|
||||
*
|
||||
* @return array|false The JSON decoded into an array
|
||||
*/
|
||||
private function format_response( $response ) {
|
||||
$this->last_response = $response;
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $response['body'] ) ) {
|
||||
return json_decode( $response['body'], true );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the response was successful or a failure. If it failed, store the error.
|
||||
*
|
||||
* @param object $response The response from the curl request.
|
||||
* @param array|false $formatted_response The response body payload from the curl request.
|
||||
*/
|
||||
private function determine_success( $response, $formatted_response ) {
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->last_error = 'WP_Error: ' . $response->get_error_message();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->last_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( in_array( $this->last_code, [ 200, 204 ], true ) ) {
|
||||
$this->is_success = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $formatted_response['error_description'] ) ) {
|
||||
$this->last_error = 'Bad Request' === $formatted_response['error_description'] ?
|
||||
esc_html__( 'Bad request. Please check the code.', 'rank-math' ) : $formatted_response['error_description'];
|
||||
return;
|
||||
}
|
||||
|
||||
$this->last_error = esc_html__( 'Unknown error, call get_response() to find out what happened.', 'rank-math' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset request.
|
||||
*/
|
||||
private function reset() {
|
||||
$this->last_code = 0;
|
||||
$this->last_error = '';
|
||||
$this->is_success = false;
|
||||
$this->last_response = [
|
||||
'body' => null,
|
||||
'headers' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token when user login.
|
||||
*/
|
||||
public function refresh_token() {
|
||||
// Bail if the user is not authenticated at all yet.
|
||||
if ( ! Authentication::is_authorized() || ! Authentication::is_token_expired() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$response = $this->get_refresh_token();
|
||||
if ( ! $response ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false === $response['success'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tokens = Authentication::tokens();
|
||||
|
||||
// Save new token.
|
||||
$this->token = $response['access_token'];
|
||||
$tokens['expire'] = $response['expire'];
|
||||
$tokens['access_token'] = $response['access_token'];
|
||||
Authentication::tokens( $tokens );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the new refresh token.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_refresh_token() {
|
||||
$tokens = Authentication::tokens();
|
||||
if ( empty( $tokens['refresh_token'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_get(
|
||||
add_query_arg(
|
||||
[
|
||||
'code' => $tokens['refresh_token'],
|
||||
'format' => 'json',
|
||||
],
|
||||
Authentication::get_auth_app_url() . '/refresh.php'
|
||||
)
|
||||
);
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
if ( empty( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke an OAuth2 token.
|
||||
*
|
||||
* @return boolean Whether the token was revoked successfully.
|
||||
*/
|
||||
public function revoke_token() {
|
||||
Authentication::tokens( false );
|
||||
delete_option( 'rank_math_google_analytic_profile' );
|
||||
delete_option( 'rank_math_google_analytic_options' );
|
||||
delete_option( 'rankmath_google_api_failed_attempts_data' );
|
||||
delete_option( 'rankmath_google_api_reconnect' );
|
||||
|
||||
return $this->is_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log every failed API call.
|
||||
* And kill all next scheduled event if failed count is more then three.
|
||||
*
|
||||
* @param array $response Response from api.
|
||||
* @param string $action Action performing.
|
||||
* @param string $start_date Start date fetching for (or page URI for inspections).
|
||||
* @param array $args Array of arguments.
|
||||
*/
|
||||
public function log_failed_request( $response, $action, $start_date, $args ) {
|
||||
if ( $this->is_success() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$option_key = 'rankmath_google_api_failed_attempts_data';
|
||||
$reconnect_google_option_key = 'rankmath_google_api_reconnect';
|
||||
if ( empty( $response['error'] ) || ! is_array( $response['error'] ) ) {
|
||||
delete_option( $option_key );
|
||||
delete_option( $reconnect_google_option_key );
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit maximum 10 failed attempt data to log.
|
||||
$failed_attempts = get_option( $option_key, [] );
|
||||
$failed_attempts = ( ! empty( $failed_attempts ) && is_array( $failed_attempts ) ) ? array_slice( $failed_attempts, -9, 9 ) : [];
|
||||
$failed_attempts[] = [
|
||||
'action' => $action,
|
||||
'args' => $args,
|
||||
'error' => $response['error'],
|
||||
];
|
||||
|
||||
update_option( $option_key, $failed_attempts, false );
|
||||
|
||||
// Number of allowed attempt.
|
||||
if ( 3 < count( $failed_attempts ) ) {
|
||||
update_option( $reconnect_google_option_key, 'search_analytics_query' );
|
||||
return;
|
||||
}
|
||||
|
||||
as_schedule_single_action(
|
||||
time() + 60,
|
||||
"rank_math/analytics/get_{$action}_data",
|
||||
[ $start_date ],
|
||||
'rank-math'
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* Google URL Inspection API.
|
||||
*
|
||||
* @since 1.0.84
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\modules
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Google;
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Analytics class.
|
||||
*/
|
||||
class Url_Inspection extends Request {
|
||||
/**
|
||||
* URL Inspection API base URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $api_url = 'https://searchconsole.googleapis.com/v1/urlInspection/index:inspect';
|
||||
|
||||
/**
|
||||
* Access token.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $token = [];
|
||||
|
||||
/**
|
||||
* Main instance
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Url_Inspection
|
||||
*/
|
||||
public static function get() {
|
||||
static $instance;
|
||||
|
||||
if ( is_null( $instance ) && ! ( $instance instanceof Url_Inspection ) ) {
|
||||
$instance = new Url_Inspection();
|
||||
$instance->setup();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup token.
|
||||
*/
|
||||
public function setup() {
|
||||
if ( ! Authentication::is_authorized() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens = Authentication::tokens();
|
||||
$this->token = $tokens['access_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send URL to the API and return the response, or false on failure.
|
||||
*
|
||||
* @param string $page URL to inspect (relative).
|
||||
*/
|
||||
public function get_api_results( $page ) {
|
||||
$lang_arr = \explode( '_', get_locale() );
|
||||
$lang_code = empty( $lang_arr[1] ) ? $lang_arr[0] : $lang_arr[0] . '-' . $lang_arr[1];
|
||||
|
||||
$args = [
|
||||
'inspectionUrl' => untrailingslashit( Helper::get_home_url() ) . $page,
|
||||
'siteUrl' => Console::get_site_url(),
|
||||
'languageCode' => $lang_code,
|
||||
];
|
||||
|
||||
set_time_limit( 90 );
|
||||
|
||||
$workflow = 'inspections';
|
||||
$this->set_workflow( $workflow );
|
||||
|
||||
$response = $this->http_post( $this->api_url, $args, 60 );
|
||||
|
||||
$this->log_failed_request( $response, $workflow, $page, func_get_args() );
|
||||
|
||||
if ( ! $this->is_success() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inspection data.
|
||||
*
|
||||
* @param string $page URL to inspect.
|
||||
*/
|
||||
public function get_inspection_data( $page ) {
|
||||
$inspection = $this->get_api_results( $page );
|
||||
if ( empty( $inspection ) || empty( $inspection['inspectionResult'] ) ) {
|
||||
return;
|
||||
}
|
||||
$inspection = $this->normalize_inspection_data( $inspection );
|
||||
|
||||
$inspection['page'] = $page;
|
||||
|
||||
return $inspection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize inspection data.
|
||||
*
|
||||
* @param array $inspection Inspection data.
|
||||
*/
|
||||
private function normalize_inspection_data( $inspection ) {
|
||||
$incoming = $inspection['inspectionResult'];
|
||||
$normalized = [];
|
||||
|
||||
$map_properties = [
|
||||
'indexStatusResult.verdict' => 'index_verdict',
|
||||
'indexStatusResult.coverageState' => 'coverage_state',
|
||||
'indexStatusResult.indexingState' => 'indexing_state',
|
||||
'indexStatusResult.pageFetchState' => 'page_fetch_state',
|
||||
'indexStatusResult.robotsTxtState' => 'robots_txt_state',
|
||||
'mobileUsabilityResult.verdict' => 'mobile_usability_verdict',
|
||||
'mobileUsabilityResult.issues' => 'mobile_usability_issues',
|
||||
'richResultsResult.verdict' => 'rich_results_verdict',
|
||||
'indexStatusResult.crawledAs' => 'crawled_as',
|
||||
'indexStatusResult.googleCanonical' => 'google_canonical',
|
||||
'indexStatusResult.userCanonical' => 'user_canonical',
|
||||
'indexStatusResult.sitemap' => 'sitemap',
|
||||
'indexStatusResult.referringUrls' => 'referring_urls',
|
||||
];
|
||||
|
||||
$this->assign_inspection_values( $incoming, $map_properties, $normalized );
|
||||
|
||||
$normalized = apply_filters( 'rank_math/analytics/url_inspection_map_properties', $normalized, $incoming );
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign inspection field value to the data array.
|
||||
*
|
||||
* @param array $raw_data Raw data.
|
||||
* @param string $field Field name.
|
||||
* @param string $assign_to Field name to assign to.
|
||||
* @param array $data Data array.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function assign_inspection_value( $raw_data, $field, $assign_to, &$data ) {
|
||||
$data[ $assign_to ] = $this->get_result_field( $raw_data, $field );
|
||||
|
||||
if ( is_array( $data[ $assign_to ] ) ) {
|
||||
$data[ $assign_to ] = wp_json_encode( $data[ $assign_to ] );
|
||||
} elseif ( preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/', $data[ $assign_to ], $matches ) ) {
|
||||
// If it's a date, convert to MySQL format.
|
||||
$data[ $assign_to ] = date( 'Y-m-d H:i:s', strtotime( $matches[0] ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Date is stored as TIMESTAMP, so the timezone is converted automatically.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a field from the inspection result.
|
||||
*
|
||||
* @param array $raw_data Incoming data.
|
||||
* @param string $field Field name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_result_field( $raw_data, $field ) {
|
||||
if ( false !== strpos( $field, '.' ) ) {
|
||||
$fields = explode( '.', $field );
|
||||
|
||||
if ( ! isset( $raw_data[ $fields[0] ] ) || ! isset( $raw_data[ $fields[0] ][ $fields[1] ] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $raw_data[ $fields[0] ][ $fields[1] ];
|
||||
}
|
||||
|
||||
if ( ! isset( $raw_data[ $field ] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $raw_data[ $field ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign inspection field values to the data array.
|
||||
*
|
||||
* @param array $raw_data Raw data.
|
||||
* @param array $fields Map properties.
|
||||
* @param array $data Data array.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function assign_inspection_values( $raw_data, $fields, &$data ) {
|
||||
foreach ( $fields as $field => $assign_to ) {
|
||||
$this->assign_inspection_value( $raw_data, $field, $assign_to, $data );
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user