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,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;
}
}

View File

@@ -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 );
}
}

View File

@@ -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';
}
}

View File

@@ -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'] );
}
}

View File

@@ -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
}
}

View File

@@ -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'
);
}
}

View File

@@ -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 );
}
}
}